char* Func_1(void) {
char str[30] = "Bruce";
printf("str: %s\n", str); // 正常打印 "Bruce"
return str; // 返回局部數組首地址
}
int main() {
char *p = Func_1();
printf("p: %s\n", p); // 打印亂碼或崩潰!
return 0;
}
函數內打印正常,調用者卻拿到垃圾數據。為什麼?
Func_1 被調用 → 棧上分配幀,str 在棧上 (如地址 0x7ffc1234)。str 初始化為 "Bruce",棧上內容有效。return str 將地址 0x7ffc1234 傳回。Func_1 返回 → 棧幀銷毀,棧指針回退,0x7ffc1234 以上內存標記為可用。main 拿到地址 0x7ffc1234,但該內存已失效,下一次函數調用或中斷處理即會覆寫。| 方式 | 代碼 | 內存區域 | 生命週期 | 適用場景 |
|---|---|---|---|---|
| 靜態局部變量 | static char str[30] = "Bruce"; return str; | 全局/靜態區 | 程式全周期 | 單例緩衝區、非線程安全 |
| 字符串字面量 | return "Bruce"; | 常量區 | 程式全周期 | 只讀字符串常量 |
| 動態分配 | char *p = malloc(30); strcpy(p, "Bruce"); return p; | 堆區 | 手動 free まで | 可變長、調用者負責釋放 |
| 調用者提供緩衝區 | void Func(char *buf, size_t n) { strncpy(buf, "Bruce", n); } | 調用者棧/堆 | 調用者控制 | 最安全、零分配、線程安全 |
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
const char *testValue = "BruceZhang";
char gstr[30] = {0}; // 全局數組,安全
char* Func_1(void); // 錯誤示範:返回局部數組地址
char* Func_2(void); // 正確:返回全局數組地址
int main(void) {
char *func_1 = Func_1();
char *func_2 = Func_2();
printf("func_1: %s\n", func_1); // 亂碼
printf("func_2: %s\n", func_2); // 正常 "BruceZhang"
// 動態分配版本
char *func_3 = malloc(30);
strcpy(func_3, testValue);
printf("func_3: %s\n", func_3);
free(func_3);
return 0;
}
char* Func_1(void) {
char str[30] = "Bruce";
printf("str in Func_1: %s\n", str);
return str; // 危險!
}
char* Func_2(void) {
strcpy(gstr, testValue);
printf("gstr in Func_2: %s\n", gstr);
return gstr; // 安全:全局數組
}
$ gcc -Wall -Wextra test.c
test.c: In function 'Func_1':
test.c:12:9: warning: function returns address of local variable [-Wreturn-local-addr]
return str;
^~~
-Wreturn-local-addr (包含在 -Wall) 能精準捕獲。/W4 給出 C4172: returning address of local variable or temporary。-Werror=return-local-addr / /WX。cppcheck --enable=all test.c → Error: Return of local variable address。scan-build gcc test.c 產生 HTML 報告,含路徑追蹤。snprintf, strcpy_s 風格)。
// C++17: 返回 std::string (小字符串優化,無堆分配)
std::string Func() { return "Bruce"; }
// 或返回 std::unique_ptr 明確所有權
auto Func() { return std::make_unique(30); }
C++ RAII 機制從語言層面消滅了此類 Bug,這也是現代 C++ 推薦避免裸指標的原因之一。