C语言动态内存管理:malloc、calloc、realloc与free

C语言动态内存管理

1. 为什么需要动态内存

固定数组的大小通常在编译阶段确定。当数据规模运行时才知道,或对象需要跨越函数作用域长期存在时,可以从堆上动态申请空间。

2. malloc与free

1
2
3
4
5
6
7
8
9
10
11
#include <stdlib.h>

int *arr = malloc(count * sizeof(*arr));
if (arr == NULL)
{
/* 处理申请失败 */
}

/* 使用arr */
free(arr);
arr = NULL;

malloc 返回未初始化内存。C语言中不需要对 void * 返回值强制转换,保留原写法还能避免漏包含 <stdlib.h> 时隐藏问题。

乘法计算大小时应防止溢出:

1
2
if (count > SIZE_MAX / sizeof(*arr))
return NULL;

3. calloc

1
int *arr = calloc(count, sizeof(*arr));

calloc 接收元素个数和单个元素大小,并将所有字节初始化为0。全零字节通常对应整数0,但不能泛化为所有类型的所有语义值。

4. realloc

1
2
3
4
5
6
int *new_arr = realloc(arr, new_count * sizeof(*arr));
if (new_arr != NULL)
{
arr = new_arr;
count = new_count;
}

realloc 可能原地扩展,也可能申请新块、复制数据并释放旧块。失败时旧指针仍然有效,所以不能直接写:

1
arr = realloc(arr, new_size); // 失败会丢失旧地址

5. 常见错误

  • 忘记 free,造成内存泄漏;
  • 释放后继续访问,形成悬空指针;
  • 同一地址重复释放;
  • 释放非动态申请的地址;
  • 写入超过申请空间;
  • 使用未初始化的 malloc 内容;
  • 函数返回局部变量地址;
  • 浅拷贝含动态指针的结构体,导致重复释放。

6. 柔性数组

C99允许结构体最后一个成员为不完整数组:

1
2
3
4
5
6
7
8
9
10
11
12
struct Packet
{
size_t length;
unsigned char data[];
};

struct Packet *packet = malloc(sizeof(*packet) + data_size);
if (packet != NULL)
{
packet->length = data_size;
free(packet);
}

结构头和变长数据一次分配,减少内存碎片并改善局部性。

7. 程序内存区域

典型进程包括代码区、只读数据区、已初始化数据区、BSS、堆、内存映射区和栈。具体布局由操作系统、编译器和链接器决定。

8. 总结

动态内存的核心是所有权:谁申请、谁释放、何时失效、容量多大。每个动态对象都应有明确生命周期,并检查所有分配和扩容结果。