動態內存分配賦予 C 程序在運行時按需獲取、調整、釋放內存的能力,是實現動態數據結構(鏈表、動態數組、樹、圖)的基礎。本文系統講解三大分配函數、釋放機制、常見錯誤模式與工程級最佳實踐。
| 函數 | 原型 | 特點 | 典型用途 |
malloc | void* malloc(size_t size) | 分配 size 字節,不初始化(含垃圾值),速度最快 | 大塊內存、性能敏感、後續會完全覆寫 |
calloc | void* calloc(size_t nmemb, size_t size) | 分配 nmemb × size 字節,全 0 初始化,略慢 | 結構體、指針數組、需零值語義的場景 |
realloc | void* realloc(void* ptr, size_t size) | 調整現有塊大小,可能移動內存;ptr=NULL 等價 malloc;size=0 等價 free | 動態數組擴縮容 |
二、核心使用模式
1. 基本分配與檢查
#include <stdlib.h>
double *arr = malloc(n * sizeof(double));
if (!arr) { // 必須檢查 NULL
perror("malloc failed");
exit(EXIT_FAILURE);
}
// 使用 arr...
free(arr);
arr = NULL; // 防懸空
2. calloc 的零值優勢
// 結構體指針成員自動為 NULL,整數為 0
typedef struct { int *data; size_t len; } Vec;
Vec *v = calloc(1, sizeof(Vec)); // v->data == NULL, v->len == 0
3. realloc 安全擴容模式
void *tmp = realloc(ptr, new_size);
if (!tmp) {
// 原 ptr 仍有效,數據未丟失
handle_oom();
} else {
ptr = tmp; // 只有成功才更新指針
}
致命錯誤:ptr = realloc(ptr, ...); 失敗時 ptr 變 NULL,導致原內存洩漏且無法釋放。
三、釋放與內存洩漏防範
- 一一對應:每個
malloc/calloc/realloc 成功調用,必有對應 free。
- 釋放後置 NULL:
free(p); p = NULL; 封裝為宏 FREE(p)。
- 所有權清晰:誰分配誰釋放,或文檔註明轉移所有權。
- 工具檢測:Valgrind (
--leak-check=full)、AddressSanitizer (-fsanitize=address)。
四、動態數組實戰封裝
typedef struct {
int *data;
size_t size; // 當前元素數
size_t capacity; // 已分配容量
} DynArray;
void da_init(DynArray *a) {
a->data = NULL;
a->size = a->capacity = 0;
}
int da_push(DynArray *a, int val) {
if (a->size == a->capacity) {
size_t new_cap = a->capacity ? a->capacity * 2 : 4;
int *tmp = realloc(a->data, new_cap * sizeof(int));
if (!tmp) return -1;
a->data = tmp;
a->capacity = new_cap;
}
a->data[a->size++] = val;
return 0;
}
void da_free(DynArray *a) {
free(a->data);
a->data = NULL;
a->size = a->capacity = 0;
}
五、進階主題
- 對齊分配:C17
aligned_alloc(alignment, size)、POSIX posix_memalign,用於 SIMD、硬件 DMA、鎖自由數據結構。
- 自定義分配器:內存池、對象池、區域分配器,減少系統調用、提高局部性、實現確定性釋放。
- 柔性數組成員 (C99):
struct { size_t len; char data[]; },單次分配承載元數據與變長數據。
- 調試技巧:
MALLOC_CHECK_=3 (glibc)、efence、重寫 malloc/free 記錄調用棧。
六、錯誤模式速查表
| 錯誤代碼 | 後果 | 修正 |
free(ptr); free(ptr); | Double Free → 堆損壞/崩潰 | 釋放後置 NULL |
malloc(n); // 忘記 sizeof | 分配過小 → 越界寫入 | malloc(n * sizeof(Type)) |
realloc(ptr, 0); // 不檢查返回 | 等價 free,但 ptr 未置 NULL | 統一用 FREE(ptr) 宏 |
ptr = realloc(ptr, sz); // 直接賦值 | 失敗導致原內存洩漏 | 用臨時變量接收 |
| 釋放棧/全局/字面量指針 | 未定義行為/崩潰 | 僅 free 堆指針 |
動態內存管理的核心心法是敬畏所有權、嚴守邊界、工具輔助。將分配/釋放封裝為項目級工具函數,配合靜態分析與運行時檢測,能將內存錯誤率壓制到極低水平。
- 上一篇文章: C語言 void* 通用指針深度解析:規則、陷阱與泛型編程實戰
- 下一篇文章: C程序性能優化十大實戰技巧:從函數調用到內存對齊
Copyright ©
程式碼之家 All Rights Reserved