記憶體管理是 C 語言程式設計的核心難點,亦是面試高頻考點。本文透過經典 itoa 錯誤返回事例,系統剖析棧、堆、靜態區的生命週期差異,給出三種修正方案並對比優劣,最後總結記憶體分配原則與 sizeof 誤區,助你構建穩健的記憶體模型。
char *itoa(int n) {
char retbuf[20];
sprintf(retbuf, "%d", n);
return retbuf; // 錯誤:返回棧上陣列地址
}
現象:呼叫 char *str5 = itoa(5) 後,str5 指向的內容不可預期,極大機率非 "5"。
根因:retbuf 為函數內部區域陣列,存放於棧區。函數返回時棧幀銷毀,記憶體標記為可覆寫,指針懸空。
| 方案 | 做法 | 優點 | 缺點 |
|---|---|---|---|
| 1. static 區域變量 | static char retbuf[20]; | 簡單,生命週期贯穿整個程式 | 不可重入、執行緒不安全、後續呼叫覆蓋前值 |
| 2. 堆上分配 | char *retbuf = malloc(20); | 生命週期可控,支援併發 | 呼叫者必須 free,否則洩漏;介面耦合分配責任 |
| 3. 呼叫者提供緩衝區(推薦) | char *itoa(int n, char *retbuf) | 職責清晰,無洩漏風險,可重入,效能最優 | 介面稍顯繁瑣 |
char *itoa(int n, char *retbuf) {
sprintf(retbuf, "%d", n);
return retbuf;
}
// 使用
char buf[20];
itoa(5, buf);
malloc/free 手動管理,忘記釋放即洩漏。「自己使用的記憶體最好自己申請和釋放。」
sizeof 陷阱char *p = malloc(1024);
int nMemSize = sizeof(p); // 錯誤:得到指針大小(4/8 bytes),而非 1024
sizeof 僅在編譯期計算型別大小,無法查詢動態分配塊大小。需自行記錄或封裝管理結構。
char *GetString(void) {
char p[] = "hello world";
return p; // 編譯器警告:返回區域陣列地址
}
雖然指針值不變,但指向記憶體已標記可複用,內容隨時可能被覆寫,屬未定義行為。
參考成熟編碼規範(如林銳《高質量 C/C++ 編程指南》),將經驗內化為團隊標準,涵蓋:
記憶體問題隱蔽且破壞力強,掌握區域模型、明確所有權、善用工具檢測,是寫出可靠 C 代碼的必修課。