动态内存管理❀C
创始人
2024-02-10 23:17:27

目录

  • ❀动态内存管理的意义
  • ❀动态内存管理函数
    • malloc - 申请空间
      • free - 释放空间
    • calloc - 申请空间
    • realloc - 调整空间大小
  • ❀常见的动态内存错误
    • 对NULL指针的解引用操作 - err
    • 对动态开辟空间的越界访问 - err
    • 对非动态开辟内存使用free释放 - err
    • 使用free释放一块动态开辟内存的一部分 - err
    • 对同一块动态内存多次释放 - err
    • 动态开辟内存忘记释放(内存泄漏)
  • ❀❀❀
  • ❀柔性数组
    • 柔性数组的特点
    • 柔性数组的优势

❀动态内存管理的意义

基本的内存开辟方式:

int val = 20;//在栈空间上开辟四个字节
int arr1[10];//在栈空间上开辟40个字节的连续空间

但是以上内存开辟的内存空间大小是固定的,不能后续自动改变空间大小。
当实际需要的空间大小比事先开辟的大小小就会造成空间浪费,当不够就需要再另外开辟空间。
但是对于空间的需求,不仅仅是上述的情况。
有时候我们需要的空间大小在程序运行的时候才能知道,编译时开辟空间的方式就不能满足了。
因此就需要动态内存管理的能力。且C语言提供了一些相关函数。

❀动态内存管理函数

在这里插入图片描述

malloc - 申请空间

在这里插入图片描述
在这里插入图片描述
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针。

因此对malloc的返回值一定要做检查。

  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者强制类型转换成需要的类型。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

1、申请成功

//√
#include int main()
{//申请空间//void* p = malloc(40);//在堆区开辟指定大小的空间后返回该空间的起始地址int* p = (int*)malloc(INT_MAX);//用void*指针接收不方便,而此时想存放整形型据就用整型指针接收//检查返回值if (p == NULL){//申请失败perror("malloc");//打印malloc:错误信息return 1;}//申请成功//初始化int i = 0;for (i = 0; i < 10; i++){*p = i;p++;}return 0;
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、申请失败

	int* ptr = (int*)malloc(INT_MAX);//申请的空间太大

在这里插入图片描述

free - 释放空间

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的。
在这里插入图片描述

  • 如果指向的空间不是动态开辟的,那free函数的行为是未定义的。
int main()
{//申请空间int* ptr = (int*)malloc(40);//40Bint* p = ptr;//检查返回值if (p == NULL){//申请失败perror("malloc");return 1;}//申请成功//初始化int i = 0;for (i = 0; i < 10; i++){*p = i;p++;}释放空间//free(p);//× - p++后此时p指向的不再是起始位置了free(ptr);return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

	//释放空间free(ptr);//ptr还指向已经被释放的不属于自己的空间,此时ptr就是野指针,因此还要继续将ptr置成空指针*ptr = 100;//err非法访问
	//释放空间free(ptr);ptr = NULL;if (ptr != NULL){*ptr = 100;}
//√
//写法1:
int main()
{//申请空间int* ptr = (int*)malloc(40);	int* p = ptr;if (p == NULL){//申请失败perror("malloc");return 1;}//申请成功//初始化int i = 0;for (i = 0; i < 10; i++){*p = i;p++;}//释放空间free(ptr);ptr = NULL;return 0;
}
//√
//写法2:
int main()
{//申请空间int* ptr = (int*)malloc(40);if (ptr == NULL){//申请失败perror("malloc");return 1;}//申请成功//初始化int i = 0;for (i = 0; i < 10; i++){*(ptr + i) = i;}//释放空间free(ptr);ptr = NULL;return 0;
}
  • 如果 memblock 是NULL指针,则函数什么事都不做。
int main()
{int* p = NULL;free(p);//什么事都不发生return 0;
}

必须释放空间吗?
当我们不释放动态申请的内存的时候,如果程序结束,动态申请的内存由操作系统自动回收,但是如果程序不结束,动态内存是不会自动回收的,就会形成内存泄露的问题。

int main()
{while (1){malloc(1000);}return 0;
}
//会耗干内存,造成死机

在这里插入图片描述

calloc - 申请空间

1、

使用malloc开辟空间:

int main()
{int*p = (int*)malloc(40);return 0;
}

#
使用malloc申请的内存空间都是随机值。

使用calloc申请内存空间:

在这里插入图片描述
在这里插入图片描述

int main()
{//申请10个整形的空间int* p = (int*)calloc(10, sizeof(int));//sizeof(int) - 每个元素的大小return 0;
}

在这里插入图片描述
calloc申请的空间会被初始化为0。

喜欢初始化为0就可以选用calloc了。

2、

int main()
{//申请10个整形的空间int* p = (int*)calloc(10, sizeof(int));//开辟失败if (p == NULL){perror("calloc");return 1;}//使用int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p + i));}printf("\n");free(p);p = NULL;return 0;
}

在这里插入图片描述
malloc的底层实现非常复杂。

realloc - 调整空间大小

1、使用场景:

在这里插入图片描述
在这里插入图片描述
有时会我们发现过去申请的空间太小了,有时候又会觉得申请的空间过大了,realloc 函数可以对动态开辟内存的大小做灵活的调整。

2、realloc在调整内存空间的存在两种情况:
(1)当原空间后有足够的未用空间时,直接追加开辟新的空间,返回整个空间的起始地址(同原起始地址);
(2)当没有足够未用的空间时,realloc会重新在空间中的其它位置找一块足够未用的空间,先将原空间的数据复制到新空间,然后再追加,realloc会将原空间主动free掉,返回新空间的起始地址;
在这里插入图片描述

int main()
{int*p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}//使用int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;//0 1 2 3 4 5 6 7 8 9}//空间仍不够,希望能放20个元素,考虑扩容 - realloc//int* p = (int*)realloc(p, 80);//当找不到足够的空间扩容失败返回空指针时,还会弄丢原空间int*ptr = (int*)realloc(p, 80);//80Bif (ptr != NULL){p = ptr;}//扩容成功了,开始使用//不再使用,就释放free(p);p = NULL;return 0;
}

❀常见的动态内存错误

对NULL指针的解引用操作 - err

开辟空间失败的时候,有可能出现对空指针解引用的操作。

int main()
{int* p = (int*)malloc(1000);//解决办法:对malloc函数的返回值进行判断,防止空指针int i = 0;if (p == NULL){//....return 1;}//使用for (i = 0; i < 250; i++){*(p + i) = i;}free(p);p = NULL;return 0;
}

对动态开辟空间的越界访问 - err

动态开辟空间的方式类似于数组,也是一片连续的内存空间。
解决:对内存边界要自行主动检查。

int main()
{int* p = (int*)malloc(100);//25个整型空间int i = 0;if (p == NULL){//....return 1;}使用越界访问了//for (i = 0; i <= 25; i++)//访问到第26个整型空间for (i = 0; i < 25; i++){*(p + i) = i;}return 0;
}

对非动态开辟内存使用free释放 - err

int main()
{int a = 10;int* p = &a;//.....free(p);//free不能释放不是动态开辟的空间,编译会报错p = NULL;return 0;
}

对于局部变量,出作用域时编译器会主动释放。而动态开辟的空间如果程序不结束,不会主动释放空间,需要free来帮助释放。

使用free释放一块动态开辟内存的一部分 - err

int main()
{int* p = (int*)malloc(100);if (p == null){return 1;}//使用int i = 0;for (i = 0; i < 10; i++){*p = i;p++;}//释放空间free(p);//err - 此时p在++后指向的不是起始地址了p = null;return 0;
}

在这里插入图片描述

用free释放必须从起始位置开始释放。
必须提前记住起始地址。避免内存泄露。

对同一块动态内存多次释放 - err

int main()
{int* p = malloc(100);if (p == NULL)return 1;free(p);//....free(p);//errp = NULL;return 0;
}

动态开辟内存忘记释放(内存泄漏)

1、

void test()
{int* p = malloc(100);使用忘记释放//free(p);//p = NULL;
}
int main()
{test();//.....while (1){;}//已经无法释放return 0;
}

2、
即使成对使用 malloc 和 free ,仍可能内存泄漏。

void test()
{int* p = malloc(100);//使用if (1)return;free(p);p = NULL;//没机会执行free,仍然会内存泄漏
}

❀❀❀

1、

#include void GetMemory(char* p)
{p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}
int main()
{Test();return 0;
}

程序崩溃。
原因:(1)内存泄漏;(2)对NULL的解引用操作。
p是str的临时拷贝,函数调用结束后,形参变量p会被销毁,但p指向的动态空间并没有被释放,造成内存泄漏;而str仍指向空指针,strcpy造成对空指针的非法访问。

注意:

int main()
{char* p = "hehe\n";//p指向的是字符串的首地址 - h的地址printf("hehe\n");//传给printf函数的是字符串的首地址char arr[] = "hehe\n";printf(arr);return 0;
}

在这里插入图片描述

//√
#include void GetMemory(char** p)
{*p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(&str);strcpy(str, "hello world");printf(str);free(str);str = NULL;
}
int main()
{Test();return 0;
}

2、

char* GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}
int main()
{Test();return 0;
}

在这里插入图片描述
返回栈空间地址的问题。
局部变量数组p的内存空间在函数调用结束后会销毁,又返回了数组p的首地址,str将非法访问一块未知的空间,此时str就是野指针。

int* test()
{int a = 10;return &a;
}int main()
{int* p = test();printf("%d\n", *p);printf("%d\n", *p);return 0;
}

在这里插入图片描述
如果空间释放后栈帧空间的数据没有被覆盖,则可能能够访问得到原数据,一旦被覆盖就找不到原数据了。

注意与以下代码作区分:以下是正确代码;

int test()
{int a = 10;return a;
}
int main()
{int n = test();return 0;
}

3、

#include void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}
int main()
{Test();return 0;
}
//√
#include void GetMemory(char** p, int num)
{*p = (char*)malloc(num);//判断是否申请成功//...
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);//没有释放free(str);str = NULL;
}
int main()
{Test();return 0;
}

4、

void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL)//释放后使用造成str成野指针{strcpy(str, "world");printf(str);}
}
int main()
{Test();return 0;
}
//√
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);str = NULL;if (str != NULL){strcpy(str, "world");printf(str);}
}
int main()
{Test();return 0;
}

❀柔性数组

C99 中,结构中的最后一个元素允许是未知大小的数组,即 柔性数组成员。

柔性数组的特点

  • 结构中的柔性数组成员前面必须至少一个其他成员。
struct S1
{int num;double d;int arr[0];//柔性数组成员
};
struct S2
{int num;int arr[];//柔性数组成员
};
  • sizeof 返回的这种结构大小不包括柔性数组的内存。
struct S2
{int num;//4int arr[];//柔性数组成员
};int main()
{printf("%d\n", sizeof(struct S2));//?return 0;
}

在这里插入图片描述

  • 包含柔性数组成员的结构要用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
#include struct S2
{int num;//4int arr[];//柔性数组成员
};
int main()
{	//struct S2* ps = (struct S2*)malloc(sizeof(struct S2));//× - 只为num开辟了空间struct S2* ps = (struct S2*)malloc(sizeof(struct S2)+40);//根据情况为柔性数组分配预期空间,此时期望柔性数组内可以放10个整型if (ps == NULL){perror("malloc");return 1;}//访问numps->num = 100;//访问柔性数组 - 同普通数组int i = 0;for (i = 0; i < 10; i++){ps->arr[i] = i;}for (i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}//释放free(ps);ps = NULL;return 0;
}

在这里插入图片描述

  • 方便对数组的扩容。
#include struct S2
{int num;//4int arr[];//柔性数组成员
};
int main()
{struct S2* ps = (struct S2*)malloc(sizeof(struct S2)+40);//根据情况为柔性数组分配预期空间,此时期望柔性数组内可以放10个整型if (ps == NULL){perror("malloc");return 1;}//访问numps->num = 100;//访问柔性数组 - 同普通数组int i = 0;for (i = 0; i < 10; i++){ps->arr[i] = i;}//for (i = 0; i < 10; i++)//{//	printf("%d ", ps->arr[i]);//}//扩容struct S2* ptr = (struct S2*)realloc(ps, sizeof(struct S2)+80);//为柔性数组扩容80Bif (ptr == NULL){perror("realloc\n");return 1;}else{ps = ptr;}//继续访问柔性数组for (i = 10; i < 20; i++){ps->arr[i] = i;}for (i = 0; i < 20; i++){printf("%d ", ps->arr[i]);}//释放free(ps);ps = NULL;return 0;
}

在这里插入图片描述

柔性数组的优势

struct S3
{int num;int* arr;
};int main()
{struct S3* ps = (struct S3*)malloc(sizeof(struct S3));if (ps == NULL){return 1;}ps->arr = (int*)malloc(40);if (ps->arr == NULL){free(ps);ps = NULL;return 1;}//使用//...//释放 - 注意释放顺序free(ps->arr);ps->arr = NULL;free(ps);ps = NULL;return 0;
}

以上代码同样可以调节数组的大小,但在实现细节上会有所差异。

优势1: 方便内存释放。
柔性数组的空间是和其它变量一次申请的,是一片连续的内存空间,使用完通过一次就可以释放完。
而以上的方法需要多次申请,多次释放。

优势2: 有利于访问速度。
对在内存中连续存放的数据的访问速度比非连续存放的访问速度快。
连续的内存有益于提高访问速度,也有益于减少内存碎片的浪费。

高并发内存池!
在这里插入图片描述
其实实际上,两种动态开辟方式相差不大,都可以使用。

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...