欢迎光临
我们一直在努力

C 语言动态内存管理

在 C 语言开发中,栈上固定大小的数组和变量,很难应对运行时才确定空间大小的场景。动态内存管理(malloc/free、calloc、realloc、柔性数组)正是解决这一痛点的核心能力,也是面试高频考点。本文从原理、用法、避坑、笔试题到内存布局,全面总结动态内存管理的核心知识。

一、为什么需要动态内存分配?

我们常规的内存开辟方式有两个致命局限:

  • 大小固定:栈上变量、数组的空间在编译时就确定,运行时无法修改;
  • 数组长度受限:声明数组必须指定固定长度,无法适配动态需求。

而动态内存分配在堆区申请空间,运行时确定大小、可灵活调整、手动释放,完美解决上述问题,适配数组扩容、变长结构体等场景。

二、核心函数:malloc、free、calloc、realloc

所有动态内存函数均在 <stdlib.h> 头文件中声明,操作的是堆区内存。

1. malloc:申请未初始化的连续空间

  • 函数原型:void* malloc(size_t size);
  • 功能:向堆区申请size 字节的连续空间,返回空间起始地址;
  • 返回值:成功返回void*地址(需强制类型转换),失败返回NULL(必须判空);
  • 注意:申请的内存未初始化,值为随机垃圾数据;size=0行为未定义。

2. free:释放动态内存

  • 函数原型:void free(void* ptr);
  • 功能:释放ptr指向的堆区动态内存;
  • 注意:
    • ptr必须是malloc/calloc/realloc返回的地址,否则行为未定义;
    • ptr=NULL时,free无任何操作;
    • 释放后建议将ptr置为NULL,避免野指针。

3. calloc:申请并初始化的连续空间

  • 函数原型:void* calloc(size_t num, size_t size);
  • 功能:申请num 个、每个 size 字节的连续空间,自动初始化为 0;
  • 与 malloc 区别:多了一步全 0 初始化,适合需要初始值为 0 的场景。

4. realloc:调整动态内存大小

  • 函数原型:void* realloc(void* ptr, size_t size);
  • 功能:调整ptr指向的内存为size字节,保留原数据;
  • 两种扩容场景:
  • 原空间后有足够空间:直接追加,返回原地址;
  • 原空间后无足够空间:开辟新空间、拷贝原数据、释放旧空间,返回新地址;
  • 注意:
    • ptr=NULL时,等价于malloc(size);
    • 失败返回NULL,原内存不变,切勿直接用原指针接收返回值(会导致内存泄漏)。

      三、动态内存的 6 大常见错误(高频踩坑点)

      1. 对 NULL 指针解引用

      malloc 失败返回 NULL,直接解引用会导致程序崩溃。

    • 2. 越界访问

      访问超出申请范围的内存,破坏堆结构,导致未定义行为。

      c

      int *p = (int*)malloc(10*sizeof(int));
      for(int i=0; i<=10; i++)
      *(p+i)=i; // 错误:i=10时越界

      3. free 非动态内存

      free 只能释放堆区内存,释放栈区变量会报错。

      int a=10;
      int *p=&a;
      free(p); // 错误:a在栈区,非动态内存

      4. free 动态内存的一部分

      指针偏移后指向非起始地址,free 失败。

      int *p=(int*)malloc(100);
      p++;
      free(p); // 错误:p不再指向起始地址

      5. 重复释放

      同一块内存多次 free,破坏堆结构。

      int *p=(int*)malloc(100);
      free(p);
      free(p); // 错误:重复释放

      6. 内存泄漏

      忘记释放不再使用的动态内存,占用系统资源,程序长期运行会崩溃。

      void test()
      {
      int *p=(int*)malloc(100);
      *p=20;
      } // 错误:未free,内存泄漏

      四、经典笔试题解析(必掌握)

      题目 1:传值调用,malloc 无效

      void GetMemory(char *p)
      {
      p=(char*)malloc(100);
      }
      void Test()
      {
      char *str=NULL;
      GetMemory(str);
      strcpy(str,"hello world"); // 崩溃
      }

      结果:程序崩溃。

    • 原因:传值调用,函数内修改的是形参 p,实参 str 仍为 NULL,解引用崩溃。

    • 题目 2:返回栈区地址,野指针

      char* GetMemory(void)
      {
      char p[]="hello world";
      return p;
      }
      void Test()
      {
      char *str=GetMemory();
      printf(str); // 乱码/崩溃
      }

      结果:输出乱码或崩溃。                                                                                                  原因:p 是栈区数组,函数结束后栈帧销毁,返回的地址为野指针。

    • 题目 3:传址调用,正常运行

    • void GetMemory(char **p, int num)
      {
      *p=(char*)malloc(num);
      }
      void Test()
      {
      char *str=NULL;
      GetMemory(&str,100);
      strcpy(str,"hello");
      printf(str); // 输出hello
      free(str); // 正确释放
      }

      结果:正常输出hello。

    • 原因:传二级指针,修改实参 str 指向堆区内存,可正常使用和释放。

    • 题目 4:释放后野指针,非法访问

      void Test()
      {
      char *str=(char*)malloc(100);
      strcpy(str,"hello");
      free(str); // 释放内存
      if(str!=NULL)
      {
      strcpy(str,"world"); // 非法访问
      printf(str);
      }
      }

      结果:输出world(侥幸),但属于未定义行为。

    • 原因:free 后 str 为野指针,值非 NULL,访问已释放内存,风险极高。

    • 五、柔性数组:结构体变长的优雅方案

      1. 定义

      C99 标准规定:结构体最后一个成员可以是未知大小的数组,称为柔性数组。

      // 写法1:部分编译器支持
      struct st
      {
      int i;
      int a[0]; // 柔性数组
      };
      // 写法2:通用
      struct st
      {
      int i;
      int a[]; // 柔性数组
      };

      2. 特点

    • 柔性数组前至少有一个成员;
    • sizeof(结构体)不包含柔性数组(如上结构体大小为 4);
    • 需用malloc一次性分配:结构体大小 + 柔性数组空间。
    • 优势:

    • 释放简单:一次 free 释放所有内存,避免用户漏释放;
    • 访问高效:内存连续,减少内存碎片,提升访问速度。
    • 六、C/C++ 程序内存区域划分

      程序运行时内存分为 4 大区域,动态内存管理核心在堆区:

    • 栈区(stack):存放局部变量、函数参数、返回地址;自动分配释放,容量小、效率高;
    • 堆区(heap):存放动态内存(malloc/free);手动分配释放,容量大、灵活;
    • 数据段(静态区):存放全局变量、静态变量;程序结束由系统释放;
    • 代码段:存放函数二进制代码;只读,程序运行期间不变。
    • 七、总结

      动态内存管理是 C 语言的核心技能,核心要点:

    • 动态内存操作堆区,需手动malloc申请、free释放;
    • malloc判空、realloc用临时指针接收、释放后置NULL;
    • 避开 6 大常见错误,掌握 4 道经典笔试题逻辑;
    • 柔性数组是结构体变长的最优方案,兼顾效率和安全性;
    • 理清内存布局,区分栈、堆、数据段、代码段的特性。
赞(0)
未经允许不得转载:171主机测评 » C 语言动态内存管理
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址