C语言学习笔记20260711-C语言动态内存管理四大核心函数详解及用例

核心头文件: <stdlib.h>
适用场景: 运行时才知道大小、需要跨函数共享数据、处理大型数据结构


一、四大核心函数详解

1. malloc - 分配未初始化内存

函数原型
void *malloc(size_t size);
功能说明

在堆区分配一块大小为 size 字节的连续内存,内存内容未初始化(垃圾值)。

代码示例
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    // 分配10个int的空间
    int *arr = (int *)malloc(10 * sizeof(int));

    // ⚠️ 必须检查返回值
    if (arr == NULL) {
        perror("malloc failed");
        return 1;
    }

    // 未初始化的内存是垃圾值,必须手动赋值
    for (int i = 0; i < 10; i++) {
        arr[i] = i * 2;
    }

    printf("arr = %d\n", arr);  // 输出: arr = 10

    free(arr);
    arr = NULL;  // 防止悬空指针

    return 0;
}
关键要点
  • 返回 void*,C语言中可隐式转换,但建议显式强转提高可读性
  • 分配失败返回 NULL,必须检查
  • 内存内容是未定义的垃圾值

2. calloc - 分配并清零内存

函数原型
void *calloc(size_t num, size_t size);
功能说明

分配 num 个大小为 size 字节的内存块,并自动将所有字节初始化为0。

代码示例
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    // 分配10个int,且全部初始化为0
    int *arr = (int *)calloc(10, sizeof(int));

    if (arr == NULL) {
        perror("calloc failed");
        return 1;
    }

    // 验证是否全为0
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr[i]);  // 输出: 0 0 0 0 0 0 0 0 0 0
    }
    printf("\n");

    free(arr);
    arr = NULL;

    return 0;
}
关键要点
  • 等价于 malloc(num * size) + memset(ptr, 0, num * size)
  • 适合数组、结构体、哈希表等需要清零的场景
  • 性能略低于 malloc(多了清零操作)

3. realloc - 调整内存大小

函数原型
void *realloc(void *ptr, size_t size);
功能说明

调整已分配内存块的大小,行为逻辑如下:

参数情况等价操作说明
ptr == NULLmalloc(size)等同于分配新内存
size == 0 且 ptr != NULLfree(ptr)等同于释放内存
原空间足够原地扩展返回原指针
原空间不足重新分配+拷贝+释放旧块返回新指针
⚠️ 致命陷阱

如果 realloc 失败返回 NULL,原内存块依然有效且未释放。直接用原指针接收返回值会导致内存泄漏。

代码示例
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *arr = (int *)malloc(5 * sizeof(int));
    if (arr == NULL) return 1;

    for (int i = 0; i < 5; i++) arr[i] = i;

    // ✅ 正确用法:用临时指针接收返回值
    int *tmp = (int *)realloc(arr, 10 * sizeof(int));
    if (tmp == NULL) {
        perror("realloc failed");
        free(arr);  // realloc失败时,原内存仍有效,必须手动释放
        return 1;
    }
    arr = tmp;  // 确认成功后再赋值给原指针

    // 新增的空间内容是未初始化的
    for (int i = 5; i < 10; i++) arr[i] = i * 10;

    for (int i = 0; i < 10; i++) printf("%d ", arr[i]);
    printf("\n");

    free(arr);
    arr = NULL;

    return 0;
}
关键要点
  • 永远用临时指针接收返回值
  • 扩容后新增的内存未初始化
  • 缩容时数据会被截断,但不会丢失已保留部分

4. free - 释放内存

函数原型
void free(void *ptr);
功能说明

释放之前由 malloc/calloc/realloc 分配的堆内存。

代码示例
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *p = (int *)malloc(sizeof(int));
    if (p == NULL) return 1;

    *p = 42;
    printf("*p = %d\n", *p);

    free(p);
    p = NULL;  // ✅ 释放后立即置NULL,防止悬空指针

    // free(p);  // ❌ 如果p没置NULL,这里会崩溃(重复释放)
    free(p);    // ✅ free(NULL) 是安全的,什么都不做

    return 0;
}
关键要点
  • 只能释放堆内存,不能释放栈变量、全局变量、字符串常量
  • free(NULL) 是安全操作,什么都不做
  • 重复释放是未定义行为,会导致堆损坏
  • 释放后指针变成悬空指针,必须置 NULL

二、常见错误与最佳实践

错误速查表

错误类型说明后果预防措施
未检查返回值malloc 失败返回 NULL,直接使用段错误每次分配后必须检查 == NULL
内存泄漏分配后未 free,或指针丢失内存耗尽遵循"谁分配谁释放"原则
悬空指针free 后未置 NULL,再次使用未定义行为free(p); p = NULL; 形成肌肉记忆
重复释放同一指针 free 两次堆损坏/崩溃置 NULL 后 free 是安全的
越界访问读写超出分配大小的内存未定义行为始终记录分配大小,使用工具检测
realloc 错误用法ptr = realloc(ptr, size)失败时内存泄漏用临时指针接收,成功后再赋值
释放栈内存free(&local_var)崩溃只释放堆内存
释放后使用访问已 free 的内存未定义行为释放后立即置 NULL

最佳实践清单

  1. 分配必检查:每次 malloc/calloc/realloc 后检查返回值
  2. 释放必置空:free(p); p = NULL; 写成固定套路
  3. realloc用临时指针:避免失败时内存泄漏
  4. 记录分配大小:用变量或结构体成员保存大小,避免越界
  5. 谁分配谁释放:明确内存所有权,避免重复释放或泄漏
  6. 函数返回前检查:确保所有路径都正确释放内存
  7. 使用工具检测:不要依赖肉眼排查内存问题

三、实战综合示例

动态数组封装

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int *data;
    size_t size;
    size_t capacity;
} DynArray;

// 初始化
int dynarray_init(DynArray *arr, size_t init_cap) {
    arr->data = (int *)calloc(init_cap, sizeof(int));
    if (arr->data == NULL) return -1;
    arr->size = 0;
    arr->capacity = init_cap;
    return 0;
}

// 追加元素
int dynarray_push(DynArray *arr, int value) {
    if (arr->size >= arr->capacity) {
        size_t new_cap = arr->capacity * 2;
        int *tmp = (int *)realloc(arr->data, new_cap * sizeof(int));
        if (tmp == NULL) return -1;
        arr->data = tmp;
        arr->capacity = new_cap;
    }
    arr->data[arr->size++] = value;
    return 0;
}

// 释放
void dynarray_free(DynArray *arr) {
    free(arr->data);
    arr->data = NULL;
    arr->size = 0;
    arr->capacity = 0;
}

int main(void) {
    DynArray arr;
    if (dynarray_init(&arr, 4) != 0) {
        perror("init failed");
        return 1;
    }

    for (int i = 0; i < 10; i++) {
        if (dynarray_push(&arr, i * i) != 0) {
            perror("push failed");
            dynarray_free(&arr);
            return 1;
        }
    }

    for (size_t i = 0; i < arr.size; i++) {
        printf("%d ", arr.data[i]);
    }
    printf("\n");

    dynarray_free(&arr);
    return 0;
}

四、总结口诀

分配必检查,释放必置空。
Realloc 用临时,越界要命凶。
谁分配谁释放,工具勤检测。
堆上无小事,谨慎保平安。


最后更新:2026-07-11

更多推荐