目录
引言
许多实际场景中,程序需要的空间大小只有在运行时才能知道(比如读取用户输入、处理可变长度的数据、构建链表/树等数据结构)。如果仅靠静态数组或局部变量,要么造成空间浪费(分配过大),要么导致溢出(分配过小)。
动态内存管理让程序员可以在堆上按需申请和释放内存,赋予程序极大的灵活性,同时也能精确控制对象的生命周期,避免全局/静态变量的持久开销。
C标准库提供了四个核心的动态内存管理函数,都声明在
<stdlib.h>中:
一、内存空间开辟
1.malloc
①函数原型
#include <stdlib.h> void* malloc(size_t size);
参数 :
size------ 请求分配的字节数 ,类型为size_t(通常是无符号整型)。返回值:
成功:返回一个指向至少
size字节的连续内存块首地址的指针。该指针已对齐,可安全地转换为任何类型的指针。失败:返回
NULL。注意:返回的指针需要使用者自行显式转换为目标类型(在 C 中可选,但推荐)。
②函数行为
在进程的堆(heap)上分配一块指定大小的连续内存。
不初始化 :分配的内存内容是未定义的 (可能是之前的残留数据),如需零值初始化请使用
calloc。分配的内存块独立于程序的其他存储区(栈、静态区),其生命周期由程序员显式控制:从分配成功到被
free()释放,或直至程序结束。
③使用示例
#include <stdlib.h> // 分配 10 个 int 的数组 int main() { int *arr = malloc(10 * sizeof(int)); if (!arr) { perror("malloc failed"); exit(EXIT_FAILURE); } // 使用 arr[0] 到 arr[9] ... // ... free(arr); return 0; }
④使用细节
使用前必须检查返回值
int *p = malloc(1000); *p = 42; // 如果 p == NULL,立即崩溃建议始终使用 sizeof 约束使用的字节数,避免假设类型大小
// 错误:假设 int 为 4 字节 int *arr = malloc(10 * 4); // 正确 int *arr = malloc(10 * sizeof(int)); // 更保险:用指针所指向的类型 int *arr = malloc(10 * sizeof *arr);不要弄丢原始指针指向的位置
int *p = malloc(10); p++; // 指针偏移 free(p); // 未定义行为!空间使用完毕后及时 free ,防止内存泄漏
void func() { int *p = malloc(100); if (err) return; // 忘记 free(p) → 泄漏 free(p); }
2.calloc
①函数原型
#include <stdlib.h> void* calloc(size_t num, size_t size);
参数:
num:要分配的元素个数。
size:每个元素的大小(字节)。返回值:
成功时返回指向分配内存的指针(已对齐,适合任何类型);
失败时返回
NULL。注意:返回的指针需要使用者自行显式转换为目标类型(在 C 中可选,但推荐)。
②函数行为
计算总分配大小 =
num * size。在堆上分配一块连续内存,至少能容纳该大小。
将这块内存的每一个字节都初始化为 0。
③使用示例
#include <stdlib.h> int main() { // 分配包含 100 个 double 的数组,全部为 0.0 double *arr = calloc(100, sizeof(double)); if (!arr) { // 处理失败 } // 使用 arr ... free(arr); return 0; }
④使用细节
- 使用前必须检查返回值
- 不要弄丢原始指针指向的位置
- 空间使用完毕之后及时 free ,防止内存泄漏
3.realloc
①函数原型
#include <stdlib.h> void* realloc(void *ptr, size_t new_size);
参数:
ptr:指向之前由malloc、calloc或realloc返回的内存块的指针。如果为NULL,行为等同于malloc(new_size)。
new_size:新内存块所需的字节数。返回值:
成功:返回指向新内存块首地址的指针(可能与原地址相同也可能不同)。
失败:返回
NULL,且原内存块保持不变(这是关键保证)。注意:返回的指针需要使用者自行显式转换为目标类型(在 C 中可选,但推荐)。
②函数行为
realloc负责将ptr所指向的已分配内存块的大小调整为new_size字节,同时尽可能保留原有数据。具体行为可分为以下几种情况:
扩展内存(
new_size > 原大小)
如果当前内存块后面有足够的空闲空间,
realloc会直接扩展该块,原地址不变,新增的字节未初始化(内容不确定)。如果后方空间不足,
realloc会执行以下步骤:
分配一个大小为
new_size的新内存块。将原内存块中的全部数据逐字节拷贝到新块(拷贝大小为原大小,而非新大小)。
释放原内存块。
返回新块的地址。
扩展后多出的字节区域没有初始化 ,与
malloc相同。缩小内存(
new_size < 原大小)
realloc可能直接缩小原块并返回原指针(尾部分割回堆),也可能重新分配并拷贝(某些实现或碎片策略决定)。无论哪种实现,截断部分的数据将丢失,但剩余部分的数据保持原样。
通常缩容操作不会失败 (除非
new_size为 0 的特殊情况)。
new_size == 0且ptr != NULL
可能等同于
free(ptr)并返回NULL。也可能返回一个唯一指针(不可解引用),此时仍需调用
free释放。可移植代码应避免依赖这种行为 ,要么显式调用
free(ptr)后将ptr置NULL,要么保证不传入 0 大小。
ptr == NULL
③使用示例
#include <stdio.h> #include <stdlib.h> int main() { int capacity = 5; int count = 0; int *arr = malloc(capacity * sizeof(int)); if (!arr) return 1; int input; while (scanf("%d", &input) == 1) { if (count == capacity) { capacity *= 2; int *tmp = realloc(arr, capacity * sizeof(int)); if (!tmp) { printf("内存不足\n"); free(arr); return 1; } arr = tmp; } arr[count++] = input; } // 使用 arr ... free(arr); return 0; }
④使用细节
使用前必须检查返回值
不要弄丢原始指针指向的位置
空间使用完毕之后及时 free ,防止内存泄漏
不允许有任何指针指向原空间下的地址(新空间创建后指向原来空间中的指针会变成野指针)
int *arr = malloc(10 * sizeof(int)); int *middle = &arr[5]; int *tmp = realloc(arr, 20 * sizeof(int)); if (tmp) arr = tmp; // middle 现在可能无效!除非能确保 arr 没变。先使用临时指针保存新的空间地址
// 危险:直接使用 int *arr = malloc(10 * sizeof(int)); arr = realloc(arr, 20 * sizeof(int)); // 若失败,arr 变为 NULL,原内存丢失 // 正确做法:空间开辟成功后再使用 int *tmp = realloc(arr, 20 * sizeof(int)); if (tmp == NULL) { // 处理失败:arr 仍然有效,可继续使用或释放 perror("realloc failed"); free(arr); exit(EXIT_FAILURE); } arr = tmp; // 确认成功后再赋值
二、内存空间释放
1.free
①函数原型
#include <stdlib.h> void free(void *ptr);
参数 :
ptr------ 指向先前由malloc、calloc或realloc返回的内存块的指针。也可以是空指针(NULL)。返回值:无。
语义 :释放
ptr指向的动态内存,将其归还给堆管理器,以便后续重新分配。
②函数行为
内存所有权交还:该内存块不再属于程序,程序不得再通过任何指针访问它(读或写)。
对象生命周期结束 :存储在块内的任何对象(如结构体、数组)从逻辑上被销毁。如果这些对象包含指向其他资源的指针,必须在
free前手动清理(因为free不会递归释放它们)。不改变指针本身的值 :
free仅处理ptr指向的内存块,并不修改ptr变量本身。调用后,ptr仍然保存着原来的地址,但该地址已无效(悬挂指针)。对
NULL安全无操作 :如果ptr == NULL,free什么也不做。这是标准明确保证的。
③使用示例
#include <stdlib.h> int main() { int *p = malloc(sizeof(int) * 10); if (p) { // 使用 p ... free(p); // 不再需要,立即释放 p = NULL; // 可选但强烈推荐 } return 0; }
④使用细节
不能释放非动态分配的内存:只能
free来自malloc/calloc/realloc的指针,以下均是未定义行为 :
栈变量地址:
int x; free(&x);全局/静态变量地址
字符串字面量:
char *s = "hello"; free(s);已经释放过的指针(双重释放)
通过指针运算偏移后的地址(如
ptr+1),哪怕原指针合法不能重复释放同一块堆区地址
int *p = malloc(10); free(p); free(p); // 未定义行为!可能导致堆损坏或安全漏洞。释放后不能再次使用
int *p = malloc(sizeof(int)); *p = 42; free(p); printf("%d\n", *p); // 未定义行为,p 已是悬挂指针空间释放后及时将指针置为 NULL
