ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

C语言梦开始的地方19:动态内存管理

2026/9/4 17:01:58 拓冰建站 浏览量
C语言梦开始的地方19:动态内存管理 文章目录C语言梦开始的地方19动态内存管理为什么要有动态内存分配malloc 和 freemallocfreecalloc 和 realloccallocrealloc常见的动态内存的错误对 NULL 指针解引用对动态开辟空间的越界访问释放非动态内存只释放部分内存重复释放同一块内存内存泄漏忘记释放动态内存经典题目题目1题目2题目3题目4柔性数组柔性数组的特点柔性数组的使用C语言梦开始的地方19动态内存管理在了解动态内存管理的知识之前我们先看一下这个C/C中程序内存区域划分为什么要有动态内存分配我们之前掌握的内存开辟方法intvalue20;intarr[20]{0};但是这种开辟空间的方式有两个特点空间开辟大小是固定的。数组在申明的时候必须指定数组的长度数组空间一旦确定了大小不能调整但是对于空间的需求不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道那数组的编译时开辟空间的方式就不能满足了。C语言引入了动态内存开辟让程序员自己可以申请和释放空间就比较灵活了。申请内存malloccalloc和realloc释放内存freemalloc 和 freemallocC语言提供了一个动态内存开辟的函数voidmalloc(size_tsize);这个函数向内存申请一块连续可用的空间并返回指向这块空间的指针。如果开辟成功则返回一个指向开辟好空间的指针。如果开辟失败则返回一个NULL指针因此malloc的返回值一定要做检查。返回值的类型是void*所以malloc函数并不知道开辟空间的类型具体在使用的时候使用者自己来决定。如果参数size为0malloc的行为是标准是未定义的取决于编译器。freeC语言提供了另外一个函数free专门是用来做动态内存的释放和回收的函数原型如下voidfree(void*ptr);free函数用来释放动态开辟的内存。如果参数ptr指向的空间不是动态开辟的那free函数的行为是未定义的。如果参数ptr是NULL指针则函数什么事都不做。malloc和free都声明在stdlib.h 头文件中。malloc 和 free搭配使用#includestdio.h#includestdlib.hintmain(){intnum0;scanf(%d,num);int*ptrNULL;ptr(int*)malloc(num*sizeof(int));// 申请num个int大小的内存空间if(ptrNULL){perror(malloc);return-1;}for(inti0;inum;i){*(ptr1)0;}free(ptr);ptrNULL;// 都free了这一步是否还有必要return0;}最后代码都释放了ptr申请的内存空间还有必要再将ptr置为NULL吗有必要这一步是为了防止释放后对指针的二次使用进行的冗余操作。因为释放内存后原指针就成了野指针再将其置空是为了避免使用野指针。calloc 和 realloccallocC语言还提供了一个函数叫calloccalloc函数也用来动态内存分配。原型如下void*calloc(size_1 t num,size_tsize);函数的功能是为num个大小为size的元素开辟一块空间并且把空间的每个字节初始化为0。与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0。#includestdio.h#includestdlib.hintmain(){intnum0;scanf(%d,num);int*ptrNULL;ptr(int*)calloc(num,sizeof(int));if(ptrNULL){perror(calloc);return-1;}for(inti0;inum;i){printf(%d ,*(ptri));}printf(\n);free(ptr);ptrNULL;return0;}reallocrelloc函数的出现让动态内存管理更加灵活。有时会我们发现过去申请的空间太小了有时候我们又会觉得申请的空间过大了那为了合理的使用内存我们一定会对内存的大小做灵活的调整。那realloc函数就可以做到对动态开辟内存大小的调整。函数原型如下void*realloc(void*ptr,size_tsize);ptr是要调整的内存地址size调整之后新大小返回值为调整之后的内存起始位置。这个函数调整原内存空间大小的基础上还会将原来内存中的数据移动到新的空间。realloc在调整内存空间的是存在两种情况情况1原有空间之后有足够大的空间情况2原有空间之后没有足够大的空间情况1当是情况1 的时候要扩展内存就直接原有内存之后直接追加空间原来空间的数据不发生变化。情况2当是情况2 的时候原有空间之后没有足够多的空间时扩展的方法是在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。所以为了避免后面没有足够多的空间时可以这样处理#includestdio.h#includestdlib.hintmain(){int*ptrNULL;ptr(int*)malloc(100*sizeof(int));if(ptrNULL){perror(malloc);return-1;}// 如果直接赋值若失败则丢失原内存// ptr (int*)realloc(ptr, 1000);// 正确写法int*tempNULL;temp(int*)realloc(ptr,1000*sizeof(int));if(temp!NULL){ptrtemp;}else{free(temp);tempNULL;perror(realloc);return-2;}free(ptr);ptrNULL;return0;}上述就是C语言提供的开辟内存函数和释放内存函数的常见使用方法。常见的动态内存的错误对 NULL 指针解引用voidtest(){int*p(int*)malloc(INT_MAX/4);// 极可能失败*p10;// 若 pNULL程序崩溃free(p);}所以每次申请内存时都要进行判断这样不仅能防止上述错误出现并且在后期调试时能快速定位到错误。对动态开辟空间的越界访问voidtest(){inti0;int*p(int*)malloc(10*sizeof(int));if(NULLp){exit(EXIT_FAILURE);}for(i0;i10;i){*(pi)i;// 当i是10的时候越界访问}free(p);}释放非动态内存voidtest(){inta10;int*pa;free(p);}只释放部分内存voidtest(){int*p(int*)malloc(100);p;free(p);}p指向了第二个元素再进行free释放时指向的就不是起始地址了重复释放同一块内存voidtest(){int*p(int*)malloc(100);free(p);free(p);// 重复释放}这里和free后指针置NULL是一个道理free后ptr就成了野指针对野指针进行操作就会发生未定义行为所以每次free后最好置NULL内存泄漏忘记释放voidtest(){int*p(int*)malloc(100);if(NULL!p){*p20;}}intmain(){test();while(1);return0;}忘记释放不再使用的动态开辟的空间会造成内存泄漏。切记动态开辟的空间一定要释放并且正确释放。动态内存经典题目题目1voidGetMemory(char*p){p(char*)malloc(100);}voidTest(void){char*strNULL;GetMemory(str);strcpy(str,hello world);printf(str);}结果程序崩溃。原因C 函数参数是值传递p 是 str 的副本修改 p 不会改变 str。所以这里应该传二级指针或者返回指针。题目2char*GetMemory(){charp[]hello world;returnp;}voidTest(){char*strGetMemory();printf(str);}结果打印乱码或崩溃悬垂指针。原因p开辟在栈上生命周期随着return p; 就被编译器收回了此时接收就不知道是什么了。题目3voidGetMemory(char**p,intnum){*p(char*)malloc(num);}voidTest(){char*strNULL;GetMemory(str,100);strcpy(str,hello);printf(str);// 打印 hello}结果内存泄漏。原因未释放申请的内存题目4voidTest(){char*str(char*)malloc(100);strcpy(str,hello);free(str);if(str!NULL){strcpy(str,world);printf(str);}}结果未定义行为可能崩溃也可能正常打印。原因free后str成为野指针。free不会修改str的值所以if(str ! NULL)恒真但访问已释放内存是不安全的。解决方法就是free后置NULL柔性数组在我们了解数组章节时讲到过一个知识点就是柔性数组柔性数组是C99推出的新语法。C99 允许结构体的最后一个成员是未知大小的数组这就叫柔性数组成员。例如structst_type{intn;inta[0];// 柔性数组成员};有些编译器会报错无法编译可以改成structst_type{intn;inta[];};柔性数组的特点结构中的柔性数组成员前面必须至少一个其他成员。sizeof 返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用malloc ()函数进行内存的动态分配并且分配的内存应该大于结构的大小以适应柔性数组的预期大小。简单的理解就是sizeof(st_type);时柔性数组不参与计算使用malloc时额外分配空间给数组使用。示例typedefstructst_type{inti;inta[0];// 柔性数组成员}type_a;intmain(){printf(%d\n,sizeof(type_a));// 输出的是4return0;}柔性数组的使用那么在动态内存管理中有什么用呢我们看下面这个示例typedefstructst_type{inti;inta[0];// 柔性数组成员}type_a;intmain(){inti0;type_a*p(type_a*)malloc(sizeof(type_a)100*sizeof(int));// 业务处理p-i100;for(i0;i100;i){p-a[i]i;}free(p);return0;}那么上述代码的优势在哪里呢我们再看一下这个代码typedefstructst_type{inti;int*p_a;}type_a;intmain(){type_a*p(type_a*)malloc(sizeof(type_a));p-i100;p-p_a(int*)malloc(p-i*sizeof(int));// 业务处理for(inti0;i100;i){p-p_a[i]i;}// 释放空间free(p-p_a);p-p_aNULL;free(p);pNULL;return0;}我们对比两个代码我们会发现代码1申请内存更方便并且释放时也只需要释放一次。代码2内存申请了2次这样就可能导致内存不连续而代码1申请了1次那么代码1的内存一定是连续的这样在后续搜索时性能代码1会更优。完