C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C语言动态内存管理

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

作者:liandestiny

本文详细介绍了C语言中动态内存管理的关键函数,如`malloc`、`free`、`realloc`等及柔性数组的概念与使用,强调了灵活可控的内存分配与释放的重要性,避免内存泄漏等常见错误,通过实例分析加深理解,并对比了柔性数组与指针成员的优缺点

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

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

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

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

而实际需求往往是动态的:比如用户输入数据量不确定、列表需要随时增减元素。C 语言引入堆区动态内存分配,由程序员手动申请、释放,完美解决上述问题,灵活可控!

二、核心动态开辟函数

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

函数原型
void* malloc(size_t size);

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

函数原型
void free(void* ptr);

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

函数原型
void* calloc(size_t num,size_t size);

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

函数原型
void* realloc(void* ptr,size_t size);

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

1.对NULL指针解引用

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

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

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

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

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

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

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

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

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

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

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

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

1.传值调用,内存泄漏

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

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

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

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

3.传址调用,内存泄漏

4.free后野指针

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

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("%zu\n",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)内存布局

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

  1. 柔性数组:一次 free 全部释放,不会漏释放,不易内存泄漏。
  2. 指针成员:必须先后两次 free,任意一次遗漏都会内存泄漏。

(3)效率

(4)使用场景总结

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

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

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

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

到此这篇关于C语言动态内存管理深度解析的文章就介绍到这了,更多相关C语言动态内存管理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文