【C语言】动态内存管理

[复制链接]
发表于 2025-10-20 10:42:41 | 显示全部楼层 |阅读模式
在前面的指针学习中我们已经对地点,内存有了肯定的相识了。前面也说过地点就相当于是门牌号,内存就相当于是一块空间相当于一栋宿舍楼,一栋宿舍楼里有差别的房间大概有些是4人间或8人间,而盘算机也一样盘算机就是讲一块大内存分成如干个小的内存单元。追念完了内存我们就来说说为什么要有动态内存分配:
一,为什么要有动态内存分配

起首我们现在已经把握的内存开辟方式有如下2种:
  1. int a=20;
  2. char arr[20]={0};
复制代码
这两种空间的开辟方式有两个共同点:

  • 他们都是在栈区开辟的,开辟的巨细是固定的。
  • 数组在开辟空间的时间巨细确定后就不可以大概变革。
   但有些时间我们数组的巨细在步伐运行的时间才知道,好比果我们之前在创建数组时数组的巨细定小了那背面也没法改。要办理这一标题C语言就引入了动态内存管理,可以大概让步伐员更加机动的申请和开释空间,以是才要有动态内存分配。
  要明确动态内存分配我们就要先明确把握有关动态内存分配的几个函数,好比malloc,calloc,realloc,free这几个函数,下面我们逐一来先容:
二,malloc函数

要熟悉一个函数我们起首就要来看这个函数的参数,返回值是什么样的,功能是什么?
void* malloc (size_t size);
   1.起首这个函数的功能是一个开辟空间的函数
2.其次该函数的参数范例为size_t范例 传入的是所开辟空间的巨细
3.接着是返回值 返回的是一个void*的指针
·如果开辟空间乐成,则返回的就是一个指向你所开辟好的空间的指针(首地点)
·如果开辟空间失败,则没有一个指针指向一个空间以是返回NULL(空指针) ,因此在利用malloc函数的时间肯定要查抄malloc函数是否开辟空间乐成!
·尚有一点就是如果参数 size 为0,malloc的活动是标准是未界说的,这取决于编译器。
  这时大概会有人问:为什么返回的是void*的指针呢?
   1,起首由于该函数不会提前知道你要开辟什么范例的空间的,以是利用void*的指针来吸收并返回,由于void*的指针可以大概吸收恣意范例的数据这点我们之前也讲过,但尚有一点要留意就是void*的指针是不能直接举行解引用利用的,须要强转后才气解引用!
  相识了上面的知识后我们就来利用一下这个函数:
在利用之前不要忘了要包罗<stdlib.h>这个头文件,由于malloc这个函数是在这里边界说的。
  1. #include<stdio.h>
  2. #include<stdilb.h>
  3. int main()
  4. {
  5.         //int *p=(int*)malloc(10*sizeof(int))
  6.         int* p = (int*)malloc(40);//将malloc函数的返回值强转为 int*类型赋给指针变量p
  7.         if (*p == NULL)//开辟空间失败就返回错误信息 这一步不能少!
  8.         {
  9.                 perror("malloc");//perror是一个打印错误信息的函数
  10.                 return 1;
  11.         }
  12.         //开辟了空间后 使用
  13.         int i = 0;
  14.         for (i = 0;i < 10;i++)
  15.         {
  16.                 *(p + i) = 10;
  17.                 //p[i] = i;
  18.         }
  19.         for (i = 0;i < 10;i++)
  20.         {
  21.                 printf("%d ", *p);
  22.         }
  23.         return 0;
  24. }
复制代码

通过效果不丢脸出我们开辟的空间乐成了,而且可以正常利用。但如果开辟失败了我们却没有查抄malloc函数是否开辟空间乐成会怎么样呢?
  1. #include<stdio.h>
  2. #include<stdilb.h>
  3. int main()
  4. {
  5.         //int *p=(int*)malloc(10*sizeof(int))
  6.         int* p = (int*)malloc(INT_MAX*1000);//将malloc函数的返回值强转为 int*类型赋给指针变量p
  7.         /*if (p == NULL)
  8.         {
  9.                 perror("malloc");//如果为空就打印错误信息
  10.                 return 1;
  11.         }*/
  12.         //开辟了空间后 使用
  13.         int i = 0;
  14.         for (i = 0;i < 10;i++)
  15.         {
  16.                 *(p + i) = 10;
  17.                 //p[i] = i;
  18.         }
  19.         for (i = 0;i < 10;i++)
  20.         {
  21.                 printf("%d ", *p);
  22.         }
  23.         return 0;
  24. }
复制代码

   效果就是什么也没有,由于INT_MAX*1000是一个非常非常大的数字,内存是没有这么大的空间的此时开辟空间失败了,以是malloc返回的是空指针,而对空指针去解引用会导致未界说活动,大概会导致步伐瓦解或产生不测效果。
  空指针指向的内存地点是无效的,解引用空指针意味着试图访问该地点所存储的数据,这在大多数环境下会导致步伐运行错误。因此,在编程中应该克制对空指针举行解引用利用,可以在解引用前查抄指针是否为空。
  如果我们想知道为什么开辟空间失败的话,加上if语句的内容利用perror打印错误信息,他就会告诉我们没有富足的空间可以开辟。
创建好了空间利用完后总不能不采取吧,如果盘算机只申请空间而不开释采取掉的话就会变得非常卡顿,就如同如果我们电脑的C盘爆满就会变得很卡一样,以是我们须要开释空间这就用到了free函数。
三,free函数

为相识决采取空间的标题C语言提供了一个专门用来做动态内存的开释和采取的函数,函数原型如下:
void free (void* ptr);
   

  • 起首参数利用一个void*的指针来吸收传入的参数,利用viod*的指针方便吸收恣意数据范例的数据。 ·如果参数ptr指向的空间不是由动态内存函数开辟的话,那么free函数的活动是未界说的。 ·如果传入的值是NULL,则函数什么变乱都不做,由于没有空间须要开释。
  • 其次返回值是void 也就是不会反回任何东西。
  那怎么利用呢照旧以上面的代码作为例子:
在利用之前要包罗头文件<stdilb.h>
  1. #include<stdio.h>
  2. #include<stdilb.h>
  3. int main()
  4. {
  5.         //int *p=(int*)malloc(10*sizeof(int))
  6.         int* p = (int*)malloc(INT_MAX*1000);//将malloc函数的返回值强转为 int*类型赋给指针变量p
  7.         /*if (p == NULL)
  8.         {
  9.                 perror("malloc");//如果为空就打印错误信息
  10.                 return 1;
  11.         }*/
  12.         //开辟了空间后 使用
  13.         int i = 0;
  14.         for (i = 0;i < 10;i++)
  15.         {
  16.                 *(p + i) = 10;
  17.                 //p[i] = i;
  18.         }
  19.         for (i = 0;i < 10;i++)
  20.         {
  21.                 printf("%d ", *p);
  22.         }
  23.          free(p);//通过free(p)就是主动释放p的空间 假如我们不主动释放等程序运行结束也会被操作系统回收。
  24.          p=NULL;//p指向的空间被释放 此时p就是野指针 所以要置为NULL空指针 否则等再次使用的时候就会出现非法访问
  25.         return 0;
  26. }
复制代码
  在利用free函数时有一点须要留意就是当指向动态内存开辟的指针被开释后,该指针指指向的内存就已经不存在了以是此时该指针就酿成了野指针在指针篇我们讲过指针指向的空间被采取就会造成野指针,利用野指针就会造成非法访问以是我们要克制。
  四,calloc函数

同样C语言还提供了一个可以大概动态内存开辟的函数calloc函数,函数原型如下:
void* calloc (size_t num, size_t size);
   

  • 起首有两个参数,第一个参数为size_t范例的num,及传入的是元素个数,第二个参数也是size_t范例的size即传入的是单个元素的巨细。
    · 以是该函数的功能是为 num 个巨细为 size 的元素开辟⼀块空间,而且把空间的每个字节初始化为0(这一点与malloc函数差别)。
  • 返回值为void*范例,这一点与malloc函数雷同
  。
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. int main()
  4. {       
  5.         int *p=(int*)malloc(10*sizeof(int));
  6.         int *pp=(int*)malloc(10,sizeof(int));
  7.         int i=0;
  8.         for(i=0;i<10;i++)
  9.         {
  10.                 printf("%d ",*(p+i));
  11.         }
  12.         for(i=0;i<10;i++)
  13.         {
  14.                 printf("%d ",*(pp+i));
  15.         }
  16.         return 0;
  17. }
复制代码

我们可以很直观的看到malloc开辟空间后打印出的值是随机值,calloc函数开辟的空间打印出的全为0,即已经初始化了。我们再调试起来让各人看看他们的区别:

五,realloc函数

有了上面的malloc和calloc函数我们就可以跟机动的开辟内存空间,但是如果我们以为开辟的空间不敷大大概太大了,想要做出一些调解怎么做呢?realloc函数就是办理这一标题的。
该函数的函数原型如下:
void* realloc (void* ptr, size_t size);
   

  • 起首该函数有两个参数,第一个参数为void*范例的ptr,用于吸收须要调解内存的地点,第二个参数为size_t范例的size 即想要扩展或缩小内存的巨细;这里的巨细是总的巨细。
  • 返回值为void*范例 返回的是调解之后的内存的起始地点。
    对于第二个参数错误有个例子如下:

    留意:realloc函数调解内存空间存在两种环境
1. 如果调解的空间不是太大,原有空间之后有富足大的空间;则在原空间后再参加一段内存,原来空间的数据不发生变革。
2. 如果扩展的空间非常大,原有空间之后没有富足大的空间;则realloc会在堆区重新找一个符合巨细的连续的空间来利用并返回新空间的首地点。
举个例子你就明确了:
  1. #include<stdio.h>
  2. #include<stdilb.h>
  3. int main()
  4. {
  5.         int* p = (int*)malloc(20);//在堆区开辟5个整型的空间
  6.         if (p == NULL)
  7.         {
  8.                 perror("malloc");
  9.                 return 1;
  10.         }
  11.         //前5个元素存5
  12.         int i = 0;
  13.         for (i = 0;i < 5;i++)
  14.         {
  15.                 *(p + i) = 5;
  16.         }
  17.         //让空间变成10个整型的空间
  18.         p = (int*)realloc(p, 10 * sizeof(int));//扩展到10个整型的空间
  19.                 if (p != NULL)
  20.                 {
  21.                         //将扩展后的5个元素改成10
  22.                         for (i = 5;i < 10;i++)
  23.                         {
  24.                                 *(p + i) = 10;
  25.                         }
  26.                 }
  27.         //打印
  28.         for (i = 0;i < 10;i++)
  29.         {
  30.                 printf("%d ", p[i]);
  31.         }
  32.         free(p);
  33.         p = NULL;
  34.         return 0;
  35. }
复制代码

通过运行效果不丢脸出realloc乐成扩展了一块新的空间,但是哪种开辟方式呢?我们绘图分析大概的环境:

要判定是那种环境着实很简单我们只须要看一下指针变量p所存的地点发生改变了没有就知道了,我们调试起来让各人看看:

当步伐走到第7行时,我们调试看p的地点为0x0000001cb8cffcc8

当步伐走到21行时,阐明空间已经扩展完成了,此时p的地点与未扩展前的地点一样由此我们可以知道是第一种环境。
如果想要得到第二种环境的话我们可以适当的将追加空间的巨细变得更大一些,这里就不再演示了有爱好的读者可以自行去实验。
说完了怎样利用这些动态内存开辟的函数我们就来看看一些动态内存的错误,可以大概让我们在利用的时间只管克制这些错误。
六,常见动态内存的错误

1,对空指针NULL举行解引用利用

我们直接看例子:
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. void test()
  4. {
  5.         int *p = (int *)malloc(INT_MAX/4);
  6.         *p = 20;
  7.         free(p);
  8.         p=NULL;
  9. }
  10. int main()
  11. {
  12.         test();
  13.         return 0;
  14. }
复制代码
  这段代码起首在堆区开辟一个巨细为INT_MAX/4的空间,但是开辟后没有判定p是否为空指针,即没有判定是否开辟空间乐成。
如果开辟失败返回空指针而*p = 20;这种对空指针的解引用就会让步伐报错。
  2,对动态开辟空间的越界访问

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. void test()
  4. {
  5.         int i = 0;
  6.         int *p = (int *)malloc(10*sizeof(int));
  7.         if(NULL == p)
  8.         {
  9.                 perror("malloc");
  10.                 return 1;
  11.         }
  12.         for(i=0; i<=10; i++)
  13.         {
  14.                 *(p+i) = i;
  15.         }
  16.         free(p);
  17.         p=NULL:
  18. }
  19. int main()
  20. {
  21.         test();
  22.         return 0;
  23. }
复制代码
  这段代码起首利用malloc函数在堆区开辟一个40个字节巨细的空间,即10个整型的空间,但是我们看到for循环中i从0开始,竣事条件是i<=10如许的效果是循环了11次,多出来的一个整型就造成了非法访问。
  3对非动态开辟内存利用free开释

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. void test()
  4. {
  5.         int a = 10;
  6.         int *p = &a;
  7.         free(p);
  8.         p=NULL;
  9. }
  10. int main()
  11. {
  12.         test();
  13.         return 0;
  14. }
复制代码
  看到这段代码我们起首要明确一个点就是我们之前创建的平常变量也好,局部变量等都是在内存中的栈区来创建的,而free函数管理的是内存中堆区的空间,这种跨地域的本领是free函数不具备的以是不能利用free函数去开释栈区的空间;即对非动态开辟内存不能利用free函数举行开释。
  4,利用free开释⼀块动态开辟内存的⼀部分

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. void test()
  4. {
  5.         int *p = (int *)malloc(40);
  6.         int i=0;
  7.         if(p!=NULL)
  8.         {
  9.                 for(i=0;i<5;i++)
  10.                 {
  11.                         *p=i;
  12.                         p++;
  13.                 }
  14.         }
  15.         free(p);
  16.         p=NULL;
  17. }
  18. int main()
  19. {       
  20.         test();
  21.         return 0;
  22. }
复制代码
  上面的代码中在利用for循环完毕后,我们开辟的空间中前5个函数都已经被赋了值,但这时我们利用free函数去开释p就会报错,缘故起因是p所指向的地点发生了变革,颠末for循环已经指向了第5个空间的地点(malloc函数开辟的空间是连续的),以是这时间去开释只能开释前5个空间内存背面5个不能被开释以是报错。
  5,对同⼀块动态内存多次开释

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. void test()
  4. {
  5.         int *p = (int *)malloc(40);
  6.         int i=0;
  7.         if(p!=NULL)
  8.         {
  9.                 for(i=0;i<10;i++)
  10.                 {
  11.                         *(p+i)=i;
  12.                 }
  13.         }
  14.         free(p);
  15.         free(p);
  16.         p=NULL;
  17. }
  18. int main()
  19. {       
  20.         test();
  21.         return 0;
  22. }
复制代码
  还用上面的代码修改一下不让p指向的位置发生变革,如果我们重复的对p利用free函数开释就会报错,原理很简单,对开释过一次不存在的空间再开释显然编译器是找不到该开释的空间的以是报错。
  6,动态开辟内存忘记开释(内存走漏)

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. void test()
  4. {
  5.         int *p = (int *)malloc(100);
  6.         if(NULL != p)
  7.         {
  8.                 *p = 20;
  9.         }
  10. }
  11. int main()
  12. {
  13.         test();
  14.         while(1);
  15. }
复制代码
  如标题,如果我们利用完了空间不去开释,久而久之内存很快就会被我们用完,而这些未被开释的内存不能在被利用就相当于内存走漏了。
  以是牢记:动态开辟的空间⼀定要开释,而且准确开释。
下面来几道动态内存的经典笔试题
七,动态内存经典笔试题分析

1,标题一

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. void GetMemory(char *p)
  4. {
  5.         p = (char *)malloc(100);
  6. }
  7. void Test(void)
  8. {
  9.         char *str = NULL;
  10.         GetMemory(str);
  11.         strcpy(str, "hello world");
  12.         printf(str);
  13.         free(str);
  14.         str=NULL;
  15. }
  16. int main()
  17. {
  18.         test();
  19.         return 0;
  20. }
复制代码
以上代码的分析我们绘图展示:

2,标题二

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. char *GetMemory(void)
  4. {
  5.         char p[] = "hello world";
  6.         return p;
  7. }
  8. void Test(void)
  9. {
  10.         char *str = NULL;
  11.         str = GetMemory();
  12.          printf(str);
  13. }
  14. int main()
  15. {
  16.         Test();
  17.         return 0;
  18. }
复制代码
代码分析如下:

效果如下:

3,标题3

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. void GetMemory(char **p, int num)
  4. {
  5.         *p = (char *)malloc(num);
  6. }
  7. void Test(void)
  8. {
  9.         char *str = NULL;
  10.         GetMemory(&str, 100);
  11.         strcpy(str, "hello");
  12.         printf(str);
  13. }
  14. int main()
  15. {
  16.         Test();
  17.         return 0;
  18. }
复制代码
代码分析如下:

效果如下:

4,标题4

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. void Test(void)
  4. {
  5.         char *str = (char *)malloc(100);
  6.         strcpy(str, "hello");
  7.         free(str);
  8.         if(str != NULL)
  9.         {
  10.                 strcpy(str, "world");
  11.                 printf(str);
  12.         }
  13. }
  14. int main()
  15. {
  16.         Test();
  17.         return 0;
  18. }
复制代码
  起首按照正常的分析我们看到在malloc函数开辟完空间后就利用字符串复制函数,将“hello”复制到str中,接着free开释str的内存空间,空间开释后此时str就已经酿成了NULL即空指针了以是背面的if语句的判定就没有任何意义了。
  

但当我们将效果打印出来以后会有疑问为什么是world,前面str已经酿成了NULL空指针了吗?
   当利用free开释str的时间str酿成了空指针不假,但要留意这时后的str就是野指针了,对野指针举利用用就黑白法访问内存了,以是最好的办理办法就是在free函数后加上str=NULL实时将str置为空指针。
  以上就是有关动态内存有关的笔试题就先容完了,下面我们先容柔性数组。
八,柔性数组

大概你没有听过柔性数组,但它确实是存在的,在C99 中,布局中的末了⼀个元素允许是未知巨细的数组,这就叫做『柔性数组』成员。
那什么呢是柔性数组呢?
   在布局体中末了一个成员是数组,且该数组是未知巨细的数组,那么这个数组就叫柔性数组。
  比方我们界说一个布局体
  1. struct type
  2. {
  3.         int i;
  4.         int a[];//这就叫柔性数组成员
  5. }
复制代码
1,柔性数组的特点


  • 布局中的柔性数构成员前面必须至少一个其他成员。由于柔性数组为末了一个成员
  • sizeof 返回的这种布局巨细不包罗柔性数组的内存。
  • 包罗柔性数构成员的布局用malloc ()函数举行内存的动态分配,而且分配的内存应该大于布局的巨细,以顺应柔性数组的预期巨细。
对于第二点我们举个例子:
  1. struct stu
  2. {
  3.         int i;
  4.         int arr[];
  5. }stu;
  6. int main()
  7. {
  8.         printf("%zd\n", sizeof(stu));
  9.         return 0;
  10. }
复制代码

2,柔性数组的利用

柔性数组一样平常共同malloc函数来利用我们给出代码
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. struct s
  4. {
  5.         int n;
  6.         int arr[];
  7. };
  8. int main()
  9. {
  10.         //为n和arr数组开辟空间
  11.         struct s*ps=(struct s*)malloc(sizeof(int)+5*sizeof(int));//5*sizeof(int)就是在给数组分配空间
  12.         if(ps==NULL)
  13.         {
  14.                 perror("malloc");
  15.                 return 1;
  16.         }
  17.         //开辟成功就使用
  18.         ps->n=10;
  19.         int i=0;
  20.         for(i=0;i<5;i++)
  21.         {
  22.                 ps->arr[i]=i;
  23.         }
  24.         //空间不够想扩展空间 扩展10个字节
  25.         struct s*ptr=(struct s*)realloc(ps,sizeof(int)+10*sizeof(int));
  26.         if(ptr !=NULL)
  27.         {
  28.                 ps=ptr;
  29.                 ptr=NULL;
  30.         }
  31.         //使用完释放
  32.         free(ps);
  33.         return 0;
  34. }
复制代码
我们给出代码的分析:

固然给柔性数组利用realloc函数的时间也大概是第二种环境,即重新开辟空间的环境。
3,柔性数组的上风

我们同样模仿柔性数组设置一个在堆区分配空间的数组,代码如下:
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. struct s
  4. {
  5.         int n;
  6.         int* arr;//使用指针方便调整大小
  7. };
  8. int main()
  9. {
  10.         struct s* ps=(struct s*)malloc(sizeof(int));
  11.         if(ps==NULL)
  12.         {
  13.                 perror("malloc1");
  14.                 return 1;
  15.         }
  16.         ps->n=10;
  17.         ps->arr=(int*)malloc(5*sizeof(int));//给数组开辟空间
  18.         if(ps->arr==NULL)
  19.         {
  20.                 perror("malloc2");
  21.                 return 1;
  22.         }
  23.         //开辟完使用
  24.         int i=0;
  25.         for(i=0;i<5;i++)
  26.         {
  27.                 ps->arr[i]=i;
  28.         }
  29.         //内存不够想将数组扩容到10个整型
  30.         struct s*ptr=(struct s*)realloc(ps->arr,sizeof(int)+5*sizeof(int));
  31.         if(ptr==NULL)
  32.         {
  33.                 perror("realloc");
  34.                 return 1;
  35.         }
  36.         //释放内存 很重要一定先释放(*ps).arr 或ps->arr
  37.         free(ps->arr);
  38.         ps->arr=NULL;
  39.         free(ps);
  40.         ps=NULL;
  41.         return 0;
  42. }
复制代码
我们给出代码分析:

   总结:
对比利用柔性数组和不利用柔性数组信任当你看完上面这两个例子之后就能判定出利用柔性数组的上风,在第二种环境中,开释内存尤为要警惕,由于是额外给数组分配的空间。而利用柔性数组最初的ps指向的就是指向数组的空间,无论背面怎么扩展在开释的时间只须要开释ps就可以了。

  以是利用柔性数组有两个长处:

  • 第一个长处是:方便内存开释
  • 第二个长处是:如许有利于访问速率
有关柔心数组的先容就到这里,下面再给一张图让你更清楚的相识动态内存:
九,C/C++中步伐内存地域分别


我们给出每个区的表明:

  • 栈区:是一种内存地域,用于存储函数的局部变量、函数参数、函数返回地点等数据。
  • 堆区:堆区是一种内存地域,用于动态分配内存空间,存储步伐运行时动态创建的数据布局和对象。
  • 数据段(静态区):静态区是步伐运行时用来存储静态变量和常量的内存地域。
  • 代码段:代码段是存放步伐实验代码的内存地域。
以上就是本章的全部内容啦!本篇文章内容超等多请各位读者按需阅读噢!
末了感谢可以大概看到这里的读者,如果我的文章可以大概帮到你那我甚是荣幸,文章有任何标题都欢迎指出!制作不易还望给一个免费的三连,你们的支持就是我最大的动力!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表