一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

C语言动态内存管理深度分析

时间:2026-06-14 08:18:47 编辑:袖梨 来源:一聚教程网

在C语言编程中,栈上开辟的int 、char、定长度数组,空间大小固定、无法灵活调整。但实际开发中,很多场景需要程序运行时才确定内存大小动态扩容 / 缩容,这就必须掌握动态内存管理。

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

int vsl = 20;       在栈空间上开辟4字节
char arr[10] = {0}; 在栈空间上开辟10字节

我们之前所学的变量创建是在栈区上,根据类型字节的大小分配空间,但是这样的开辟空间方式有两个特点:

  • 空间大小固定

    编译时就确定

    ,运行中无法修改。
  • 数组长度不可变:声明时必须指定长度,一旦定义无法扩容 / 缩容。

而实际需求往往是动态的:比如用户输入数据量不确定、列表需要随时增减元素。C 语言引入

堆区

动态内存分配

,由程序员手动申请、释放,完美解决上述问题,灵活可控!

二、核心动态开辟函数

1.malloc:申请指定大小的连续内存

函数原型
void* malloc(size_t size);
  • 头文件

    #include<stdlib.h>

  • 功能:向堆区申请size字节连续可用内存,返回起始地址。
  • 参数size为申请的字节数。
  • 返回值:成功返回

    void*

    类型的起始地址(需强制类型转换);失败返回

    NULL

    (内存不足时),必须判空。
  • 注意:申请的内存

    未初始化

    ,是随机值。

C语言动态内存管理深度解析

2.free:释放动态申请的内存

函数原型
void free(void* ptr);
  • 头文件

    #include<stdlib.h>

  • 功能

    释放

    ptr指向的

    堆区动态内存

  • 参数ptr动态内存起始地址
  • 注意:如果参数ptr指向的空间

    不是动态开辟

    的,那么

    free函数的行为是未定义的

    ;如果参数ptr

    是NULL指针

    ,则函数什么事都不做。

C语言动态内存管理深度解析

3.calloc:申请并初始化内存

函数原型
void* calloc(size_t num,size_t size);
  • 功能:申请num个、每个size字节的连续内存,

    自动初始化为 0

  • 与malloc的区别:calloc会初始化内存为0,malloc是随机值
  • 返回值:同malloc,成功返回地址,失败返回NULL。

C语言动态内存管理深度解析

C语言动态内存管理深度解析

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

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

  • 有时候我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们⼀定会对内存的大小做灵活的调整。那realloc

    函数就可以做到对动态开辟内存大小的调整。
函数原型
void* realloc(void* ptr,size_t size);
  • 功能:调整ptr 指向的动态内存大小为size字节,

    保留原数据

  • 参数

    ptr为原内存地址

    ,NULL时等价于malloc;

    size为调整后的新字节数

  • 返回值:成功返回新地址(

    可能与原地址不同

    );失败返回NULL,原内存不变,不释放。
  • 扩容两种情况
    1. 原内存后有足够空间

      直接追加

      返回原地址

    2. 原内存后无空间开辟新空间拷贝原数据释放旧空间

      返回新地址

C语言动态内存管理深度解析

C语言动态内存管理深度解析

三、动态内存常见的6大错误

1.对NULL指针解引用

malloc、realloc、calloc函数返回值可能为NULL,直接解引用会崩溃。

C语言动态内存管理深度解析

2.对动态开辟空间的越界访问

申请10个int,访问第11个int,越界访问。

C语言动态内存管理深度解析

3.对非动态开辟内存使用free释放

free(栈区变量),行为未定义。

C语言动态内存管理深度解析

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

指针偏移后free,行为未定义。

C语言动态内存管理深度解析

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

同一块内存被多次释放后,程序崩溃。

C语言动态内存管理深度解析

C语言动态内存管理深度解析

6.动态开辟内存未释放(内存泄漏)

动态内存忘记释放,程序运行占用内存不释放。

C语言动态内存管理深度解析

四、动态内存经典笔试题分析

1.传值调用,内存泄漏

C语言动态内存管理深度解析

C语言动态内存管理深度解析

  • 结果:程序崩溃,内存泄漏。
  • 原因:GetMemory 是值传递,p 是 str 的临时拷贝,修改 p 不影响原 str,str 仍为 NULL,解引用崩溃;malloc 的内存无法释放,泄漏。

改进方法1:使用二级指针作为参数。

C语言动态内存管理深度解析

C语言动态内存管理深度解析

改进方法2:函数返回指针。

C语言动态内存管理深度解析

C语言动态内存管理深度解析

2.返回栈区地址,野指针

C语言动态内存管理深度解析

  • 结果:打印随机值,野指针。
  • 原因:p 是栈区局部数组,函数返回后栈帧销毁,p 的地址失效,str 指向野指针。

改进方法1:  使用静态区内存

C语言动态内存管理深度解析

3.传址调用,内存泄漏

C语言动态内存管理深度解析

  • 结果:正常打印 hello,内存泄漏,程序崩溃。
  • 原因:二级指针传递,GetMemory 修改 str 本身,指向堆区内存,合法可用;malloc 的内存无法释放,泄漏。

4.free后野指针

C语言动态内存管理深度解析

  • 结果:程序崩溃或打印随机值。
  • 原因:free 后 str 未置空,仍指向已释放的内存(野指针),非法访问。

五、柔性数组:结构体的动态数组

1.柔性数组的定义

C99 规定:

结构体

最后一个成员可以是

未知大小的数组

,称为柔性数组

// 写法1:常用
struct S 
{
    int n;
    int arr[];柔性数组成员
};
// 写法2:部分编译器支持
struct S 
{
    int n;
    int arr[0];
};

2.柔性数组的特点

  1. 结构体中至少有一个其他成员(不能只有柔性数组)。
  2. sizeof(结构体)

     不包含柔性数组的内存。
  3. 必须用malloc一次性分配结构体 + 柔性数组的内存。
struct S
{
    int n;
    char c;
    int arr[];
};
int main()
{
    printf("%zun",sizeof(struct S)); 8
    return 0;
}

3.柔性数组 vs 指针成员

struct S
{
    int n;
    int arr[];柔性数组成员
};
int main()
{
    struct S* p=(Struct S*)malloc(sizeof(struct S)+10*sizeof(int));
    1.一次malloc,同时包含结构体n+arr数组空间
    if(p==NULL)
    { 
        perror("malloc");//给出错误信息
        rerturn 1;
    }
    p->n=100;
    for(int i=0;i<10;i++)
    {
        ps->arr[i]=i+1;
    }
    free(p);//一次释放空间
    p=NULL;
    return 0;
}
    
struct S
{
    int n;
    int* arr; 指针变量
};
int main()
{
    struct S* p=(struct S*)malloc(sizeof(struct S));
    1.只开辟了int n + int* arr的大小,此时arr是野指针,没有指向任何可用数组空间
    if(p==NULL)
    {
        perror("malloc");
        return 1;
    }
    p->n=100;
    int* ptr=(int*)malloc(10*sizeof(int));
    2.单独创建arr指向的数组空间
    p->arr=ptr;
    for(int i=0;i<10;i++)
    {
        ps->arr[i]=i+1;
    }
    free(p->arr); 3.第一次释放数组数据空间
    free(p);      4.第二次释放结构体空间
    p=NULL;
    return 0;
}

(1)内存布局

  • 柔性数组:一整块连续内存,narr紧紧挨在一起。
  • 指针成员:两块分散内存,结构体和数组内存相互独立。

(2)释放区别(最大优势)

  1. 柔性数组:

    一次 free 全部释放

    ,不会漏释放,不易内存泄漏。
  2. 指针成员:

    必须先后两次 free

    ,任意一次遗漏都会内存泄漏。

(3)效率

  • 柔性数组内存连续,CPU 缓存命中率高,访问速度更快,内存碎片更少。
  • 指针成员两块内存分散,效率略低,容易产生内存碎片。

(4)使用场景总结

  1. 追求简洁、稳定、高性能:优先柔性数组(结构体动态数组首选)。
  2. 数组需要中途单独扩容、单独销毁:选用指针成员。

总结:柔性数组 = 一体连续内存,一次释放;指针成员 = 分体内存,两次释放。

六、C/C++ 程序内存区域划分

C语言动态内存管理深度解析

  1. 栈区(stack):存放局部变量、函数参数、返回地址;自动分配释放,向下增长,空间小。
  2. 堆区(heap):存放

    动态内存

    malloc/calloc/realloc),手动分配释放,向上增长,空间大。
  3. 数据段(静态区):存放全局变量、静态变量;程序结束由系统释放。
  4. 代码段:存放可执行代码、只读常量;只读,防止篡改。
  5. 内核空间:操作系统占用,用户代码不可读写。

热门栏目