数组的长度在编译时就定死了,但程序运行时往往不知道要存多少数据。这时候就需要动态内存分配:运行时按需向系统申请内存,用完再还回去。malloc 与 free 就是这套流程的"借"与"还",管理不好就会内存泄漏或崩溃。

1. 栈与堆

程序的内存大致分两块:局部变量住在上,函数返回时自动释放;动态分配的内存住在上,必须手动释放。栈像临时寄存柜,堆像仓库——仓库借了不还,系统内存就会越来越少。

2. malloc:申请内存

malloc(字节数) 在堆上申请一块内存,返回首地址(类型为 void *)。失败时返回 NULL,所以必须检查:

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

int main(void) {
    int n = 5;
    int *arr = (int *)malloc(n * sizeof(int));
    if (arr == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

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

    free(arr);       // 用完归还
    return 0;
}

推荐用 n * sizeof(int) 而不是写死 20,这样在别的平台上也不会出错。

3. calloc 与 realloc

calloc 申请内存并全部清零;realloc 调整已分配内存的大小(可能搬家到新地址):

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

int main(void) {
    int *arr = (int *)calloc(4, sizeof(int));  // 4 个元素,全为 0
    arr[0] = 10;
    arr[1] = 20;

    arr = (int *)realloc(arr, 8 * sizeof(int));  // 扩容到 8 个
    arr[7] = 99;

    for (int i = 0; i < 8; i++) {
        printf("%d ", arr[i]);   // 10 20 0 0 0 0 0 99
    }
    printf("\n");

    free(arr);
    return 0;
}

realloc 失败会返回 NULL 且原内存不动,所以别直接写 p = realloc(p, ...),否则会丢掉原指针。

4. free 与内存泄漏

每个 malloc 都要配对 free。忘记 free 就叫内存泄漏:程序跑得越久,占的内存越多,最终系统资源耗尽。在长驻服务里尤其致命。

#include <stdlib.h>

void leak_example(void) {
    int *p = (int *)malloc(100 * sizeof(int));
    // 忘了 free(p) —— 每次调用泄漏 400 字节
    p = NULL;   // 指针丢了,内存再也找不回来
}

Linux 下可用 valgrind 检测泄漏:valgrind ./program

5. 悬空指针与双重释放

free 之后指针还保存着旧地址,这叫悬空指针;对同一块内存 free 两次叫双重释放,都是未定义行为,可能崩溃。正确姿势:

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

int main(void) {
    int *p = (int *)malloc(sizeof(int));
    *p = 42;
    printf("%d\n", *p);

    free(p);
    p = NULL;       // 关键:释放后立即置空
    // free(p);     // 即使再 free 一次,对 NULL 是安全的
    return 0;
}

6. 实战:动态增长的数组

把"申请-检查-扩容-释放"串起来,写一个简单但完整的动态数组:

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

int main(void) {
    int cap = 2, size = 0;
    int *data = (int *)malloc(cap * sizeof(int));

    for (int i = 1; i <= 6; i++) {
        if (size == cap) {           // 空间不够就翻倍
            cap *= 2;
            data = (int *)realloc(data, cap * sizeof(int));
        }
        data[size++] = i * i;
    }

    for (int i = 0; i < size; i++) {
        printf("%d ", data[i]);
    }
    printf("\n容量 %d\n", cap);

    free(data);
    return 0;
}
💡 学习建议:内存管理三问——这块内存是谁申请的?谁负责释放?释放后指针置空了吗?每次写完动态内存代码都自查一遍,养成肌肉记忆。