字符串字面量存儲在只讀常量區,嘗試修改導致崩潰。本節通過崩潰案例、內存佈局圖解、正確修改姿勢、const 防護,建立字符串內存安全意識。
int main() {
char *msg = "ABC"; // msg 指向常量區
msg[0] = msg[1]; // 嘗試修改只讀內存 → 崩潰!
puts(msg);
return 0;
}
$ gcc crash.c -o crash
$ ./crash
Segmentation fault (core dumped) // Linux
// 或 Access violation (Windows)
進程虛擬地址空間(簡化):
┌─────────────────────────────────────┐
│ 內核空間 (3GB~4GB) │
├─────────────────────────────────────┤
│ 用戶空間 │
│ ┌───────────────────────────────┐ │
│ │ 棧區 (向下增長) │ │
│ │ 局部變量、函數幀 │ │
│ └───────────────────────────────┘ │
│ ┌───────────────────────────────┐ │
│ │ 堆區 (向上增長) │ │
│ │ malloc/free │ │
│ └───────────────────────────────┘ │
│ ┌───────────────────────────────┐ │
│ │ 數據區 (.data/.bss) │ │
│ │ 全局/靜態變量 │ │
│ └───────────────────────────────┘ │
│ ┌───────────────────────────────┐ │
│ │ 代碼區 (.text) │ │
│ │ 機器指令 │ │
│ └───────────────────────────────┘ │
│ ┌───────────────────────────────┐ │
│ │ 常量區 (.rodata) ← "ABC" 在此 │ │
│ │ 字符串字面量、只讀數據 │ │
│ │ **頁屬性:只讀 (READ ONLY)** │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
"ABC" 編譯時存入 .rodata 段char *msg = "ABC" 使 msg 指向只讀區msg[0] = ... 觸發寫保護頁錯誤 → SIGSEGVint main() {
char msg[] = "ABC"; // 在棧上分配 4 字節,複製 "ABC\0"
msg[0] = msg[1]; // 修改棧上副本 → 安全!
puts(msg); // 輸出 "BBC"
return 0;
}
機制:char msg[] = "ABC" 等價於 char msg[4] = {'A','B','C','\0'},編譯器生成代碼在棧幀分配空間並複製內容。
int main() {
char *msg = strdup("ABC"); // 堆上分配副本
if (!msg) return 1;
msg[0] = msg[1];
puts(msg);
free(msg); // 釋放
return 0;
}
| 寫法 | 類型 | 存儲位置 | 可修改 | 生命週期 | sizeof |
|---|---|---|---|---|---|
char *p = "ABC" | char* | .rodata (只讀) | 否 | 程序全周期 | 指針大小 |
char a[] = "ABC" | char[4] | 棧/數據區 (可寫) | 是 | 作用域/程序周期 | 4 |
const char *p = "ABC" | const char* | .rodata (只讀) | 編譯器阻止 | 程序全周期 | 指針大小 |
// 僅讀取:用 const 承諾不修改
const char *msg = "ABC";
puts(msg);
// msg[0] = 'X'; // 編譯錯誤:assignment of read-only location
// 需修改:用數組
char msg[] = "ABC";
msg[0] = 'X'; // 合法
規範:所有不修改字符串的指針參數、返回值、變量,**一律加 const**。
// 錯誤:承諾不改卻能改
void bad_print(char *str) { puts(str); }
// 正確:const 保護調用者傳入的字面量
void good_print(const char *str) { puts(str); }
// 調用
good_print("Hello"); // 合法:字面量傳給 const 指針
good_print(msg); // 合法:數組退化為 const 指針
bad_print("Hello"); // 危險:字面量傳給非 const,內部若修改會崩潰
const char *s1 = "Hello";
const char *s2 = "Hello";
printf("%p %p\n", s1, s2); // 可能相同!編譯器合併相同字面量
char a[] = "Hello";
char b[] = "Hello";
printf("%p %p\n", a, b); // 必須不同!各自在棧上獨立分配
watch *(char*)0xaddress 監視內存寫入valgrind ./program 檢測非法內存訪問-Wwrite-strings 將字面量類型視為 const char*,捕獲隱式去 constchar *p = "ABC"; p[0] = 'X'; 在 GDB 中觀察崩潰堆棧char* safe_strdup(const char* src)man 7 signal 理解 SIGSEGV、SIGBUSstd::string 構造函數接受 const char*?「字面量在只讀區,副本在可寫區」。char *p = "..." 指向只讀區,**絕不修改**;char a[] = "..." 在棧上創建副本,**可隨意修改**。const 是編譯器契約,**所有不修改的字符串指針必須加 const**。這不僅是語法規範,更是內存安全的第一道防線。