C與指針精讀第十一章:動態內存分配(上)——核心API、陷阱與防禦編程
《C和指針》第十一章專講動態內存管理。本文提煉核心 API 語義、四大經典陷阱、防禦性編程模式、可移植性最佳實踐,並補充現代 C 標準(C11/C17)新增的對齊分配、擴展檢查機制。
核心 API 語義速查表
| 函數 | 原型 | 語義 | 返回值 | 關鍵點 |
malloc | void* malloc(size_t size) | 分配 size 字節未初始化內存 | 成功:指針;失敗:NULL | 不清零,內容不定 |
calloc | void* calloc(size_t nmemb, size_t size) | 分配 nmemb×size 字節並歸零 | 同 malloc | 防溢出乘法,自動清零 |
realloc | void* realloc(void* ptr, size_t new_size) | 調整 ptr 指向塊大小至 new_size | 成功:新指針(可能移動);失敗:NULL(原塊不變) | ptr==NULL 等價 malloc |
free | void free(void* ptr) | 釋放 ptr 指向塊 | 無 | ptr 必須來自 malloc 族,free(NULL) 安全無操作 |
aligned_alloc | void* aligned_alloc(size_t alignment, size_t size) | C17:分配指定對齊內存 | 同 malloc | size 必須是 alignment 整數倍 |
四大經典陷阱與防禦代碼模式
1. 不檢查返回值 → 空指針解引用崩潰
// ❌ 危險
int *arr = malloc(100 * sizeof(int));
arr[0] = 42; // 若 malloc 失敗 → Segfault
// ✅ 標準防禦模式
int *arr = malloc(100 * sizeof(int));
if (!arr) {
fprintf(stderr, "FATAL: malloc failed at %s:%d\n", __FILE__, __LINE__);
exit(EXIT_FAILURE);
}
arr[0] = 42; // 安全
2. 越界訪問 → 堆損壞、安全漏洞
// ❌ 寫越界
int *p = malloc(10 * sizeof(int));
p[10] = 0; // 寫入第 11 個元素,破壞堆元數據
// ✅ 邊界檢查封裝
#define SAFE_SET(arr, idx, val, len) \
do { if ((idx) >= 0 && (idx) < (len)) (arr)[idx] = (val); \
else handle_oob(__FILE__, __LINE__); } while(0)
3. 釋放非堆指針 → 未定義行為
// ❌ 栈指針 free
int local = 42;
free(&local); // UB:腳本崩潰、靜默損壞
// ❌ 重複 free
free(p);
free(p); // 雙重釋放 → 堆管理器崩潰
// ✅ 防禦:釋放後置 NULL
free(p);
p = NULL; // 二次 free 安全無操作
4. 使用已釋放內存 → Use-After-Free (UAF)
// ❌ 釋放後繼續用
free(p);
printf("%d", *p); // UB:數據可能已被覆寫、頁面已解映射
// ✅ 所有權轉移模式
void take_ownership(int **ptr) {
// 做完釋放責任轉移給調用者
}
int *p = malloc(...);
take_ownership(&p); // p 所有權已轉移,此處不可再用
防禦性編程最佳實踐清單
- 分配即檢查:每個
malloc/calloc/realloc 緊跟 if (!ptr) handle_oom()
- 釋放即置空:
free(ptr); ptr = NULL; 宏封裝 #define FREE(p) do{free(p); p=NULL;}while(0)
- 柔性數組結構體:
struct { size_t len; int data[]; } 一次分配減少碎片
- 內存池/對象池:高頻小塊分配場景,避免系統調用開銷
- AddressSanitizer:編譯加
-fsanitize=address 自動檢測 UAF/雙重釋放/越界
- Valgrind:
valgrind --leak-check=full ./prog 檢測洩漏
可移植性:sizeof 與類型抽象
// ❌ 硬編碼大小(32/64位不兼容)
int *p = malloc(100 * 4); // 假設 int=4 bytes
// ✅ 類型感知分配
int *p = malloc(100 * sizeof(int));
// 或更安全:sizeof(*p) 自動適應指針指向類型
int *p = malloc(100 * sizeof(*p));
現代 C 增強(C11/C17)
aligned_alloc(alignment, size):SIMD、硬件 DMA、緩存行對齊必備
malloc_usable_size(ptr) (GNU擴展):查詢實際可用大小
posix_memalign(&ptr, alignment, size):POSIX 對齊分配
calloc 零初始化語義在安全關鍵代碼中優於 malloc+memset
內存洩漏分類與檢測