0


【C语言】深度剖析动态内存管理

文章目录

0. 前言

在平常开辟数组的时候,你是否为空间不足、空间浪费、空间无法调整而烦恼?如果对此头疼不已,相信看完这篇博客,你的问题就能迎刃而解。没错,本篇博客就是对动态内存管理的讲解。博客中,对于动态内存的相关函数、使用动态内存时经常出现的问题,和几道经典笔试题做了详细讲解。话不多说,我们这就开始。

1. 为什么存在动态内存分配

我们已经掌握的内存开辟方式有:

int val =20;//在栈空间上开辟4个字节char arr[20]={0};//在栈空间上开辟20个字节的连续空间

但是上述的开辟空间的方式有两个缺点:

  1. 空间开辟大小是固定的,无法扩容或减容,可能会空间不足或空间浪费。
  2. 数组在定义的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了(数组需要提前指定好大小,因为编译的时候需要确定函数栈空间大小,遇到运行位置才能确定大小的情况就不太适合了),那么这时不如试试动态内存开辟!

2. 动态内存函数

2.1 malloc

c语言提供了一个动态内存开辟的函数:

void*malloc(size_t size);

malloc是一个开辟动态内存的函数,参数

size

为开辟空间的内存大小,单位是字节。函数返回值为

void*

的指针,开辟成功返回开辟空间的地址,失败返回NULL空指针。

2.1.1 申请空间成功

例如,开辟一个四十个字节的空间:

#include<stdlib.h>//所需头文件intmain(){void* p =malloc(40);return0;}

但是这样使用还是不够准确的,因为p的类型是

void*

void*

的指针也不知道步长,也不能解引用,也不能±,不如我们直接将p强制类型转换成对应类型。就比如我们想申请一个10个整形元素的空间。

int* p =(int*)malloc(40);

当malloc成功申请到空间,返回这块空间的起始地址。

2.1.2 申请空间失败

倘若我们申请空间,失败了。例如我内存只有4个G,但是我要申请1个T的空间,这时就会返回NULL空指针。

所以当空间开辟失败这是很危险的,所以在每次开辟空间后最好来一个判断:

intmain(){int* p =(int*)malloc(INT_MAX);//21亿多,整形的最大值if(p ==NULL){printf("%s\n",strerror(errno));//打印错误码,了解错误return1;//异常返回}//使用return0;}

运行结果:

image-20220923163711368

这里也可以用断言,断言为直接将程序奔溃,雷厉风行;而if语句则是一个委婉的处理,让我们看到对应的错误。一般在传参时参数检查使用断言,malloc等开辟空间的函数使用if语句判断是否为空指针。

intmain(){int* p =(int*)malloc(INT_MAX);//21亿多assert(p);//断言return0;}

运行结果:

image-20220923164401800

2.1.3 总结

void*malloc(size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

  • size是需要动态开辟空间的内存大小。
  • 如果开辟成功,则返回一个指向开辟空间起始处的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候自己来决定。
  • 如果参数size为0,malloc的行为是标准未定义的,取决于编译器。

2.2 free

2.2.1 free的重要性

我们平常创建的局部变量,出作用域自动销毁,它在

栈区

上开辟。

而动态内存分配,如malloc等函数是在

堆区

上开辟空间的,它并不会自动销毁,需要自己回收,或者程序结束时自动回收。

但是程序结束时自动回收有个缺点,当这个程序不结束时,这块空间就会一直存在。试想一下,如果运行一个大规模的程序,程序运行的周期很长,但是动态内存一直在开辟空间,也不释放,最后会不会因为内存不足,导致内存耗干?导致电脑卡死?

然后就会出现某些灵异现象,程序一跑起来就很卡,过一会程序结束就没了,或者没有关掉程序,然后电脑越来越卡,只能重启,重启完毕又好了的事情,如果内存没有及时释放,你说多恐怖?这件就是典型的"吃内存"现象。

所以C语言提供了另外一个参数free,专门用来做动态内存的释放和回收:

voidfree(void* ptr);

free用来释放动态内存开辟的空间,参数ptr为指向开辟空间的首地址处的指针。函数没有返回值。若参数为动态开辟的起始地址,释放空间。若参数为NULL空指针,则不进行操作。

2.2.2 free的使用

例如:

intmain(){int* p =(int*)malloc(40);if(p ==NULL){printf("%s\n",strerror(errno));//打印错误码,了解错误return1;//异常返回}//使用int i =0;for(i =0; i <10; i++){*p = i;
        p++;//改变指向,p最后指向空间的结尾后面位置}//释放free(p);//ok?return0;}

这样做可不可行?答案是不行的,因为使用动态开辟的空间时,p被修改了,这时p释放的不是我们开辟的空间,这样就出问题了。

我们应该额外保存一份p的拷贝,用拷贝进行使用,最后再释放p的空间:

intmain(){int* p =(int*)malloc(40);int* ptr = p;if(p ==NULL){printf("%s\n",strerror(errno));//打印错误码,了解错误return1;//异常返回}//使用int i =0;for(i =0; i <10; i++){*ptr = i;
        ptr++;//改变指向,p最后指向空间的结尾后面位置}//释放free(p);
    p =NULL;//及时置空
    ptr =NULL;//最好这样,防止把ptr误用return0;}

注意:

p需要及时置为空指针。当对p进行释放时,p对应的空间被置为

随机值

,但是p本身的地址还没有改变。这是很麻烦的,万一有人不知道,又使用了之前开辟的空间,这块空间我们已经还给操作系统无法使用了,这时访问了就属于非法访问,所以要及时置为空指针,让它无法被访问。

image-20220924095546648

设想一下,如果我们把p释放了,但是没有置空,那它是不是个野指针,对应着

指针的指向被释放

。野指针很危险,现在拿NULL把它限制住了,我们不就安全了?

但是这里最好把ptr也置为空指针,因为ptr当前指向了不属于我们当前程序的空间,为防止误用,还是置空。但是ptr不用free,因为ptr指向的空间不是我们动态开辟的。

2.2.3 总结

void*free(void* ptr);
  • ptr是值为动态内存开辟的起始地址的指针。
  • free释放的是动态开辟的指针ptr指向的空间,而不是ptr本身,指针需指向开辟空间的首地址处。
  • 如果ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果ptr是NULL空指针,则函数什么事都不做。
  • free释放空间后,需要将ptr置为空指针,防止野指针问题(指针指向空间被释放),造成非法访问。

2.3 calloc

倘若我们已经有了明确的目的我们要开辟多大的空间,类型是什么。那么我们就可以使用calloc函数。

calloc和malloc一样,也是由C语言提供,用来动态内存分配:

void*calloc(size_t num,size_t size);

calloc也是动态内存开辟空间的一个函数,参数

num

为开辟空间的元素个数,参数

size

为开辟空间元素的大小,单位是字节。函数返回值为

void*

,开辟成功返回开辟空间的地址,失败返回NULL空指针。

2.3.1 calloc的使用

和malloc一样,calloc返回值也是

void*

,所以我们在使用时需要强制类型转换。

例如开辟一个40字节,用来存储整形的空间:

intmain(){int* p =(int*)calloc(10,sizeof(int));if(p ==NULL){perror("calloc");//打印错误信息return1;}int i =0;//使用for(i =0; i <10; i++){*(p + i)= i;//不改变指向   }//释放free(p);
    p =NULL;return0;}

calloc开辟空间失败也会返回NULL,所以需要判断。并且需要释放开辟的空间,这里由于p并没有改变指向,p还是指向原来的位置,所以直接释放p置空即可。

2.3.2 malloc和calloc的区别

  1. malloc传参时直接传递开辟空间的大小,calloc传参时传元素个数和元素的大小。
  2. malloc开辟的空间默认值为随机值,calloc开辟的空间默认值为0。

image-20220923175144448

calloc相当于把开辟的空间每个元素设置为0,然后返回起始地址。相当于calloc = malloc + memset(内存设置为0)。

总结:

开辟的空间需要初始化,使用calloc,不需要初始化,使用malloc。但是malloc不初始化效率会更高,calloc效率较malloc会比较低。

2.3.3 总结

void*calloc(size_t num,size_t size);
  • num是开辟空间的元素个数,size为开辟空间元素的大小。
  • 函数的功能是开辟num个大小为size的空间,并且把空间的每个字节初始化为0.
  • 与函数malloc的区别在于calloc会在返回地址之前把申请空间的每个字节初始化为全0。

2.4 realloc

realloc函数的出现会让动态内存管理更加灵活。

有时我们发现申请的空间太小了,有时我们又会觉得申请的空间过大了,那为了合理的申请内存,我们就必须对内存大小做出灵活的调整,那么

realloc

函数就可以做到对动态内存开辟内存大小的调整。

void*realloc(void* ptr,size_t size);

realloc是调整动态开辟内存大小的函数,

ptr

为指向动态内存开辟空间的指针,

size

为调整过后这块空间的大小,单位是字节。函数返回值为

void*

,调整成功返回指向调整之后的内存块,失败返回NULL空指针。

2.4.1 realloc调整空间的两种情况

  1. 当前内存空间大小充足,则跟着原先开辟的空间继续向后开辟,返回原来的空间的起始地址。

image-20220924110627681

  1. 当前内存空间大小不够,重新寻找内存,单独开辟一块全新的空间,空间大小满足调整大小。将原先空间的数据先拷贝到当前空间,再释放掉原先的空间,返回新开辟空间的起始地址。

image-20220924111537777

  1. realloc调整后的空间比原先空间小,直接在原先空间的基础上缩短空间大小,返回原来空间的起始地址。

2.4.2 realloc的使用

对于realloc调整内存,还是要着重强调一下前两种情况:

  1. 内存足够在原有内存之后追加空间,返回原先空间的起始地址。
  2. 内存不足重新开辟调整大小的空间,先拷贝数据,在释放原先空间,返回新空间起始地址。

例如,一个realloc的正常使用:

intmain(){//动态开辟int* p =(int*)malloc(40);if(p ==NULL){return1;}//使用int  i =0;for(i =0; i <10; i++){*(p + i)= i;}//打印for(i =0; i <10; i++){printf("%d ",*(p + i));}//增加空间int* ptr =(int*)realloc(p,80);//判断if(ptr !=NULL){
        p = ptr;
        ptr =NULL;//防止ptr误使用}//扩容后使用for(i =10; i <20; i++){*(p + i)= i;}//释放free(p);
    p =NULL;return0;}

这里有几个注意点,需要重点提一下。

2.4.2.1 注意点 1

一定要接收realloc的返回值。

首先,得了解函数调整内存的情况。不要不知所云就认为

realloc

不管什么情况都是以原先空间的基础上向后延伸.

一定要返回值接收,否则当开辟空间足够大,返回新空间的地址时,如果我们不用返回值接收,就像这样:

intmain(){int* p =(int*)malloc(8000);if(p ==NULL){return1;}//使用int  i =0;for(i =0; i <10; i++){*(p + i)= i;}realloc(p,80);//无返回值接收//扩容后使用for(i =10; i <20; i++){*(p + i)= i;}free(p);
    p =NULL;return0;}

运行一下:
image-20220924115541942
分析:

程序直接奔溃了,因为realloc调整空间时,发现空间不足,只能找一块全新的位置开辟,将原先的空间释放掉了,而我们并没有用返回值接收调整p,那么就用p非法访问了内存。

2.4.2.2 注意点 2

用全新的指针接收realloc的返回值,而不是直接用动态开辟内存的指针接收。

我们知道realloc调整空间失败返回NULL空指针。

如果将NULL赋给原先指向开辟空间的p指针。比如,p原本指向40个字节的空间,但是空间调整失败了,直接给我弄成了空指针。这不是偷鸡不成蚀把米嘛!连原本的空间都没了,你说realloc这个老六干的什么事情!

所以我们需要用一个全新的指针来接收,比如这样:

int* ptr =(int*)realloc(p,80);

当然仅仅用返回值接收肯定不够,当然还要赋给我们之前的指针。当然在这时要对返回值做出判断,并且及时将ptr置空。因为ptr被赋值,以后这块空间就由先开始的指针进行管理并释放,为了保险起见,不让ptr影响p的操作,于是把ptr置空,防止误操作。

intmain(){int* p =(int*)malloc(40);if(p ==NULL){return1;}int* ptr =(int*)realloc(p,80);if(ptr ==NULL)//判断{
        p = ptr;
        ptr =NULL;//置空}return0;}

2.4.2.3 注意点 3

当第一个参数为NULL空指针时,realloc起到和malloc/calloc一样的作用。

intmain(){int* p =(int*)realloc(NULL,40);//等价于malloc(40)return0;}

2.4.3 总结

void*realloc(void* ptr,size_t size);
  • ptr 是要调整的内存地址
  • size 是调整之后新大小
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
  • realloc在调整内存空间的存在主要两种情况:- 情况1:原有空间之后有足够大的空间。- 情况2:原有空间之后没有足够大的空间。
  • 一定要接收realloc的返回值。
  • 用全新的指针接收realloc的返回值,而不是直接用动态开辟的指针接收。
  • ptr为NULL空指针时,realloc起到和malloc/calloc一样的作用。

2.5 malloc/calloc和free的问题

malloc和free的次数相同,不能开辟空间不释放,会造成内存泄漏。也不能多次释放,同样的对于calloc也是这样。

malloc/calloc不成对出现代码一定错误,但是malloc/calloc成对出现也可能写不出正确的代码。

举个例子:

inttest(){int* p =(int*)malloc(40);if(p ==NULL){//...return1;}//使用if(1)//某个条件满足{return2;//条件满足返回}//释放free(p);//没有释放
    p =NULL;return0;}intmain(){test();return0;}

分析:

这里malloc和free成对出现,但是由于满足条件,函数提前结束了,然后p指向的空间就没有释放,依然错误。

而p又是在函数中创建的,等函数结束,p也销毁,也并没有返回值来记住p,p在函数中指向的那块空间是被开辟的,但是出了函数就没人知道这块空间在哪里,这就造成了内存泄漏。

3. 常见的动态内存错误

动态内存虽然好用,但是使用不当就会让人十分苦恼,下面列出几个常见的错误。

3.1 对NULL指针进行解引用操作

intmain(){int* p =(int*)malloc(INT_MAX);if(p ==NULL)//判断{perror("malloc");return1;}else{*p =5;}free(p);
    p =NULL;return0;}

分析:

如果开辟空间过大,malloc开辟空间失败,返回NULL空指针,这时对指针解引用操作,程序就会奔溃。

最好的方法是对p是否为空指针进行判断,如果为空指针则打印错误信息,并退出函数。

3.2 动态开辟空间的越界访问

intmain(){int* p =(int*)malloc(20);if(p ==NULL){return1;}//使用int i =0;for(i =0; i <20; i++)//把20当做元素个数了{*(p + i)= i;//严重越界}//释放free(p);
    p =NULL;return0;}

分析:

malloc开辟了20个字节的空间,但是我误以为20为元素个数,造成了严重的越界访问,导致程序奔溃。

一定要搞懂函数的意思,在对指针进行操作时候看清楚!!!

3.3 对非动态开辟的内存使用free释放

intmain(){int num =10;int* p =&num;//...free(p);
    p =NULL;return0;}

分析:

平时创建的局部变量在栈空间上开辟,当作用域结束,变量会自动销毁。而free只作用于在堆区上开辟的空间,如果将平常开辟的内存进行释放,程序会奔溃。编译器会很凌乱,表示这届程序员真难带!!!

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

intmain(){int* p =(int*)malloc(40);if(p ==NULL){return1;}int i =0;for(i =0; i <5; i++){*p = i;
        p++;//p改变了}//释放free(p);
    p =NULL;return0;}

分析:

p在使用过程中,进行了调整,p不再指向原来动态内存开辟的空间的起始位置。这块空间可能是动态开辟内存的一部分,也可能完全不适于开辟的空间。这时运行程序,程序依然会奔溃。

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

intmain(){int* p =malloc(40);if(p ==NULL){return1;}int i =0;for(i =0; i <5; i++){*(p + i)= i;}free(p);//已经释放过了//p = NULL//加上这个就不会奔溃//...继续写代码free(p);//忘记已经释放过了return0;}

分析:

当我们对一块动态内存进行释放后,接着写代码,然后忘记自己已经对这块空间进行释放。于是我们继续释放,当程序运行起来时,程序会奔溃。

要牢记一个malloc/calloc对应一个free。

如果我们在这里把

p = NULL

,就不会有问题了。因为free对空指针时不会操作的。

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

//函数会返回动态开辟空间的地址,记得在使用之后释放int*get_memory(){int* p =(int*)malloc(40);//...return p;}intmain(){int* ptr =getmemory();//使用//没释放return0;}

分析:

函数返回了动态内存开辟的空间,我们可以对其进行使用。但是一定要释放,否则就会出现内存泄漏,也就是"吃内存"的情况。

在我们设计这个函数时就应该写好相应注释,提醒使用者。使用者也应该养成良好的习惯,对动态内存开辟的空间进行释放。

4. 几个经典的笔试题

4.1 题目1

下列程序运行结果是什么?

voidGetMemory(char*p){
    p =(char*)malloc(100);}voidTest(void){char*str =NULL;GetMemory(str);strcpy(str,"hello world");printf(str);}intmain(){Test();return0;}

运行结果:

image-20220924215350281

分析:

程序奔溃了。这里有两个问题。

第一个问题:

GetMemory

函数传参传str本身,p是str的一份临时拷贝。在函数中使用p开辟一块100个字节的动态内存的空间。也就相当于p改变了,但是str本身并没有改变。

回到Test函数中,str依然是NULL空指针。这时对str进行字符串拷贝。会对空指针进行解引用操作。程序奔溃。

值得一提的是这里的

printf(str)

并没有问题。可以通过一个简单的例子来证明:我们平时可以通过

printf("hello")

把hello打印出来,同样的我们也可以把字符串的首元素地址放入指针中,通过指针打印出字符串。因为

printf("hello")

是把h的地址传给了printf,这样打印没问题,那么我把其他部分省略,我的意思也是把地址传给printf,然后直接打印字符串。

第二个问题:

malloc开辟的空间没有释放。但是如果我们想释放也无法释放,因为在

GerMemory

函数中存放开辟空间地址的指针由于退出函数被释放了。返回

Test

函数后没人知道这块空间在哪里,也没法释放。

所以这个函数实际上是存在着很严重的问题的,所以我们接下来就将其改对。

正确写法:

  • 传址调用,直接改变str
voidGetMemory(char** p){*p =(char*)malloc(100);}voidTest(void){char* str =NULL;GetMemory(&str);strcpy(str,"hello world");printf(str);//释放free(str);
    str =NULL;}intmain(){Test();return0;}

分析:

我们要改变str的值,那么就把str的地址传入。str是一级指针,那么&str就需要用二级指针接收。通过解引用,找到str,将动态开辟空间的首地址放入p中。在使用完之后对空间进行释放。

  • 参数无意义,返回值改变str
char*GetMemory(char* p){
    p =(char*)malloc(100);return p;}voidTest(void){char* str =NULL;
    str =GetMemory(str);strcpy(str,"hello world");printf(str);//释放free(str);
    str =NULL;}intmain(){Test();return0;}

分析:

这种写法也行。但是这里的参数其实没有实际的意义,我完全可以省略参数,在函数体内创建变量,开辟动态空间,然后返回起始地址。这种方法也行,但是我不是很推荐。

以上两种方法运行结果:

image-20220924230758549

4.2 题目2

下列程序运行结果是什么?

char*GetMemory(void){char p[]="hello world";return p;}voidTest(void){char* str =NULL;
    str =GetMemory();printf(str);}intmain(){Test();return0;}

运行结果:

image-20220925123418176

分析:

str里存放的是空指针,然后调用

GetMemory

函数,在函数中在栈空间上开辟一个数组,返回p(指向数组首元素位置)。但是出了函数,在函数中开辟的数组就会被销毁。当str接收返回值时就会接收被销毁空间的地址,当我对str进行打印时,这块空间已经还给操作系统了,这块空间可能被更改,也可能没更改。当前我们对其进行打印是乱码。

这就是典型的返回栈空间地址的问题!!!

一个小细节:

刚刚说返回函数栈空间的地址不对,那么这个函数对不对?

inttest(){int a =10;return a;}intmain(){int ret =test();printf("%d\n", ret);return0;}

分析:

这个函数是完全正确的。当局部变量a返回时,会把a放到寄存器中,假设我们这个寄存器为eax,然后a销毁。再通过eax把返回值带回。

那么这个呢?

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

分析:

这就是典型的返回函数栈空间的地址。

我主函数中的p指向的空间a已经被释放,属于野指针,如果通过指针去访问,就是非法访问。

但是大家可能会有疑惑,那我这个运行结果怎么解释:

image-20220924235100478

这个其实是巧合。a所在的空间恰好没有被修改,如果我们坚信这个是对的,以后肯定是会翻车的!!!

如果我稍加改变,在打印

*p

之前打印一句话,例如这样:

intmain(){int* p =test();printf("hello\n");printf("%d\n",*p);return0;}

image-20220924235344336

这里仅仅是增加了一句话就改变了

*p

的值,这是为什么?

看过我之前函数栈帧博客的,可能好理解些,接下来简单说一下原理:

当我们调用test函数时,在main函数上方需要开辟test函数的函数栈帧。栈空间使用习惯是从高地址向低地址使用。首先在栈帧最下方开辟a变量所需空间,当返回时则将

*p

放入寄存器中,将值带回,test函数栈帧被销毁。这一时刻很巧,a的值也没有改变。但是如果我们在打印

*p

前再使用了printf函数来打印一句话。这个printf函数可能就会在原先被释放的test函数栈帧的基础上开辟栈帧空间,这时a空间中的数据可能就会被修改,这就是6的来源。

4.3 题目3

下列程序运行结果是什么?

voidGetMemory(char**p,int num){*p =(char*)malloc(num);}voidTest(void){char*str =NULL;GetMemory(&str,100);strcpy(str,"hello");printf(str);}intmain(){Test();return0;}

运行结果:

image-20220925003823533

分析:

str起始为NULL,将&str和开辟空间大小传给

GetMemory

函数,函数在内部通过

*p

找到str空间,将动态开辟空间的起始地址放入str中,在通过strcpy进行拷贝,拷贝也成功了,最后打印也没问题。

这个过程看似一气呵成,但是缺了释放动态开辟的空间!!!及时释放非常重要!!!

正确写法:

voidGetMemory(char** p,int num){*p =(char*)malloc(num);}voidTest(void){char* str =NULL;GetMemory(&str,100);strcpy(str,"hello");printf(str);free(str);
    str =NULL;}intmain(){Test();return0;}

运行结果:

image-20220925005214872

4.4 题目4

下列程序运行结果是什么?

voidTest(void){char* str =(char*)malloc(100);strcpy(str,"hello");free(str);if(str !=NULL){strcpy(str,"world");printf(str);}}intmain(){Test();return0;}

运行结果:

image-20220925182504291

分析:

malloc开辟了一块100个字节的空间,放入str中,strcpy也将"hello"放入了str中,然后我就对str空间进行释放了。但是我没有置为空指针。所以下面的if语句是会执行的,这时我使用了被释放的str,str为野指针,为非法访问。在对str进行strcpy将world放入str中,再进行打印。

虽然跑出了结果,但是它本质上是错的,只能说明编译器大意了,没有闪(doge)。我们还是要发挥主观能动性,自己发现错误,毕竟我们是程序员。

正确写法:

这个代码其实槽点挺多的,首先它释放空间后没有置空。其次它也没有开辟完空间就对str是否为空指针进行判断,所以我们不妨对它进行一个大整改。

我们在释放完空间之后直接将str置为空指针。让下面的if语句起到作用,就达到了我们原本的目的。

voidTest(void){char* str =(char*)malloc(100);if(str ==NULL){return;}strcpy(str,"hello");free(str);
    str =NULL;if(str !=NULL){strcpy(str,"world");printf(str);}}intmain(){Test();return0;}

运行结果:

image-20220925123734312

5. 结语

到这里,本篇博客就到此结束了。相信大家对动态内存管理也有了一定的了解。动态内存管理在C语言中是一块非常重要的知识,还是希望大家可以熟练掌握。

如果觉得anduin写的还不错的话,还请一键三连!如有错误,还请指正!

我是anduin,一名C语言初学者,我们下期见!

标签: c语言 c++ 算法

本文转载自: https://blog.csdn.net/m0_67867172/article/details/127036680
版权归原作者 进击的安度因 所有, 如有侵权,请联系我们删除。

“【C语言】深度剖析动态内存管理”的评论:

还没有评论