Skip to content

C 内存管理(Memory Management)

手动内存管理是 C 语言的核心难点之一。本章介绍栈与堆、malloc/calloc/realloc/free 的用法、所有权约定、常见错误与实战建议。

1. 栈与堆

  • 栈(stack):函数局部变量、参数等,自动分配与释放,容量有限。
  • 堆(heap):通过分配函数动态申请,需要手动释放,灵活但易出错。

2. 分配与释放 API

c
#include <stdlib.h>

void *p = malloc(100);        // 未初始化的 100 字节
void *q = calloc(25, 4);      // 置零的 25×4 字节
p = realloc(p, 200);          // 调整大小,可能搬迁
free(q); q = NULL;            // 释放并置空指针
free(p); p = NULL;

要点:

  • 检查返回值是否为 NULL。
  • realloc 失败时返回 NULL,原指针仍然有效且未改变(建议用临时指针接收)。
c
void *tmp = realloc(p, new_size);
if (!tmp) { /* 处理失败,p 仍然可用 */ }
else { p = tmp; }

3. 所有权与生命周期

  • 谁分配,谁释放;或者明确转移所有权。
  • 一个指针只能被 free 一次;重复释放是未定义行为。
  • 释放后指针悬空,建议立即置为 NULL。

4. 常见错误

  • 内存泄漏:忘记释放或失去引用。
  • 悬空指针:free 后继续使用。
  • 越界读写:写出缓冲区范围导致崩溃或安全问题。
  • 使用未初始化内存:malloc 得到的是未定义内容,应先初始化或使用 calloc。

5. 分配大小与溢出

  • 使用 sizeof *ptr 习惯减少类型修改时的错误:
c
int *a = malloc(n * sizeof *a);
  • 检查乘法是否溢出(n × size):
c
if (n > SIZE_MAX / sizeof *a) { /* 防止溢出 */ }

6. 动态数组与增长策略

  • 典型扩容:容量不足时扩展为 1.5× 或 2×,减少 realloc 次数。
c
size_t cap = 0, len = 0; int *arr = NULL;
void push(int v){
    if (len == cap) {
        size_t ncap = cap ? cap*2 : 8;
        int *tmp = realloc(arr, ncap * sizeof *arr);
        if (!tmp) { /* 处理失败 */ return; }
        arr = tmp; cap = ncap;
    }
    arr[len++] = v;
}

7. 与结构体配合:灵活数组成员

c
typedef struct {
    size_t len;
    int data[]; // 结尾的灵活数组(C99)
} IntVec;

IntVec *v = malloc(sizeof *v + n * sizeof v->data[0]);

8. 拷贝与移动

  • 原始内存拷贝:memcpy, memmove(重叠时用 memmove)。
  • 初始化:memset,但不要用于非平凡对象的语义初始化(C 里大多是 POD,可用)。

9. 调试建议

  • 统一创建/销毁接口,集中管理资源。
  • 使用工具检测泄漏与越界(如平台下的内存调试器)。
  • 开启编译器警告,进行边界与返回值检查。

10. 小结

遵循所有权与生命周期原则,规范使用分配 API,做好容量与边界检查,是写好 C 内存管理的关键。

本站内容仅供学习和研究使用。