【C语言】动态内存管理(malloc/free) 目录1. 为什么存在动态内存分配2、动态内存函数malloc 和 freecallocrealloc3、常见动态内存错误4、动态内存使用建议5、柔性数组柔性数组的特点柔性数组的定义柔性数组的使用1. 为什么存在动态内存分配我们已经掌握的内存开辟方式有:int val 20;//在栈空间上开辟四个字节 char arr[10] {0};//在栈空间上开辟10个字节的连续空间但是上述的开辟空间的方式有两个特点,1. 空间开辟大小是固定的。2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。但是对于空间的需求 , 不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态内存开辟了。2、动态内存函数malloc 和 free它们都包含在头文件stdlib.h中C语言提供了一个动态内存开辟的函数:void* malloc (size_t size);这个函数向内存堆区申请一块连续可用的空间,并返回指向这块空间的指针size_t size 是要开辟的字节数 。● 如果开辟成功,则返回一个指向开辟好的空间的指针。● 如果开辟失败,则返回一个 NULL 指针,因此 malloc 的返回值一定要做检查。● 返回值的类型是 void*,所以 malloc 函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。● 如果参数 size 为 0, malloc 的行为是标准未定义的,取决于编译器。应用举例int main() { //申请40个字节,用来存放10个整型 int* p (int*)malloc(40); if (p NULL) { printf(%s\n, strerror(errno)); return 1; } //存放1~10 int i 0; for (i 0; i 10; i) { * (p i) i 1; } //打印 for (i 0; i 10; i) { printf(%d , *(p i)); } return 0; }程序如果顺利结束 开辟的空间会自动换给操作系统但还是建议主动使用 free 函数来释放这块空间。free 函数void free (void* ptr);free函数用来释放动态开辟的内存● 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。● 如果参数 ptr 指向的空间不是动态开辟的空间的首地址那么程序会挂掉● 如果参数 ptr 是NULL指针,则函数什么事都不做。只需将指向开辟的空间的指针传给 free 函数即可freep。但是 free 函数并不会把 p 指针也 “释放”掉free 函数的作用就是将 p 所指向的内存空间的使用权还给操作系统p 指针仍然存在它仍然指向那块开辟的空间如果此时再使用 p 指针访问那块空间就会形成非法访问。所以在使用 free 函数之后一般连带着将 p 指针置为 NULL。什么时候 malloc 会申请失败并返回 NULL 呢如果申请的空间太大mallocINT_MAX)INT_MAX 表示 int 类型所能存储的的十进制最大值是 20 多亿的数。callocC语言还提供了一个函数叫 calloc,calloc函数也用来动态内存分配。原型如下:void* calloc (size_t num, size_t size);● 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。● 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。用 calloc 开辟的空间也要用 free 释放。realloc● realloc函数的出现让动态内存管理更加灵活。● 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。函数原型如下:void* realloc (void* ptr, size_t size);● ptr 是要调整的内存地址●size 调整之后的总大小而不是新增的的大小● 返回值为调整之后的内存起始位置● 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。● realloc在调整内存空间时存在两种情况:○ 情况1:原有空间之后有足够大的空间则直接在原有空间之后的空间续上新开辟的空间○ 情况2:原有空间之后没有足够大的空间则会另外寻找一片空间旧的数据会拷贝到新的空间并且旧的空间会被释放realloc 函数返回的指针要用一个新的指针去接收而不能用之前接收 malloc、calloc 或 realloc 的指针去接收。原因是如果 realloc 函数开辟空间失败返回 NULL这时原来开辟的空间和指向它的指针不受任何影响此时如果用原先的指针接收则会丢失原先开辟的空间的数据。int* ptr (int*)realloc(p, 10*sizeof(int)); if (ptr ! NULL) { p ptr; }如果向 realloc 函数传递一个 NULL那么此时 realloc 函数的作用与 malloc 函数一样。3、常见动态内存错误注以下代码都是错误示范一、对 NULL 指针的解引用操作也就是动态内存函数的返回值没有检查直接使用。二、对动态内存开辟的空间越界访问int main() { int*p (int*)malloc(100); //25 个整型 if (p NULL) { return 1; } int i 0; //越界访问 for (i 0; i 100; i) { *(pi) 0; } free(p) ; p NULL; return 0;三、对非动态开辟内存使用 free 释放int main() { int a 10;//栈区 int* p a; free(p); p NULL; return 0; }四、使用 free 释放动态开辟内存的一部分void test() { int *p (int *)malloc(100); p; free(p);//p不再指向动态内存的起始位置 }五、对同一块动态内存空间多次释放int main() { int* p (int*)malloc(100); if (p NULL) { return 1; } //使用 //释放 free(p); //... //忘记已经 free(p) 了 free(p); return 0; }六、动态内存忘记释放内存泄漏void test() { int* p (int*)malloc(100); //使用 // ...... } int main() { test(); // . . . . return 0; }七、函数提前结束而没有释放内存void test() { int* p (int*)malloc(100); if (NULL p) { return; } //使用 // ... if (1) { return; } //释放 free (p); p NULL; int main() { test(); return 0; }内存区域划分1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。2. 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。4、动态内存使用建议一、malloc 和 free 使用的次数malloc 使用次数越少越好因为 malloc 可能忘记 free并且 malloc 使用次数越多内存中的内存使用会“碎片化”降低了内存的利用率。二、申请大空间用 malloc小空间还是在栈上申请当使用malloc分配内存时系统会在分配的内存块前后添加一些额外的信息这些信息通常被称为cookie或header。这些cookie主要用于内存管理比如记录分配的内存块大小用于free()时知道需要释放多少内存、对齐信息、调试信息等。如果申请的空间越大cookie 的比重就越小空间浪费就越小。5、柔性数组也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。例如typedef struct st_type { int i; int a[0];//柔性数组成员 }type_a;以上的 a[0] 就是柔性数组也可以写成 a[ ]。柔性数组的特点● 结构体中的柔性数组成员前面必须至少有一个其他成员。● sizeof 返回的这种结构体大小不包括柔性数组的大小。● 定义包含柔性数组成员的结构体应该用 malloc 函数进行内存的动态分配而不是直接定义并且分配的内存应该大于结构体的大小,以此给柔性数组分配空间。柔性数组的定义定义柔性数组例子以以上结构体为例type_a* ptr type_a*mallocsizeoftype_a 10 * sizeofint; type_a* p; if(ptr NULL) { perror(malloc:); return 1; } p ptr;没有直接定义 type_a 类型的结构体 type_a a直接定义而是使用 malloc 函数进行内存的动态分配。在给结构体非柔性数组成员分配大小时sizeoftype_a还给柔性数组分配了空间 10 * sizeofint。结构体中包含的不是数组或柔性数组成员而是一个指向动态开辟内存的指针struct S { int n; char* arr; }; int main() { struct S* ps (struct S*)malloc(sizeof(struct S)); if (ps NULL) { perror(malloc); return 1; } ps-n 100; ps-arr(char*)malloc(sizeof(char)*10); if (ps-arr NULL) { perror(malloc-arr); return 1; } return 0; }两种定义方式的区别1、malloc 和 free 使用的次数第一种使用了一次而第二种使用了两次当然是 malloc 使用次数越少越好因为 malloc 可能忘记 free并且 malloc 使用次数越多内存中的内存使用会“碎片化”降低了内存的利用率。2、空间是否连续第一种空间是连续的而第二种空间可能不连续。当然是空间连续好空间越连续内存访问的效率越高。总结建议使用第一种方式定义。柔性数组的使用int i 0; //定义 type_a *p (type_a*)malloc(sizeof(type_a)100*sizeof(int)); p-i 100; //赋值 for(i0; i100; i) { p-a[i] i; } //扩容 realloc(p,sizeof(type_a)100*sizeof(int)); //释放 free(p) ;The End