崩潰代碼
char *p[3] = {"dsfs", "jasa", "ussg"}; // p 指向字串常量區 (唯讀)
strcpy(*(p+a1), *(p+a2)); // 嘗試寫入唯讀記憶體 → SIGSEGV / Access Violation
記憶體佈局圖解
代碼區 (Text) : 機器指令
----------------------------
字串常量區 (Literal Pool) : "dsfs", "jasa", "ussg" (唯讀, 共享)
0x4000: 'd''s''f''s''\0' ← p[0] 指向這裡
0x4010: 'j''a''s''a''\0' ← p[1] 指向這裡
----------------------------
全域/靜態區 (Data/BSS) : 全域變數、static 變數
----------------------------
堆區 (Heap) : malloc/new 分配
----------------------------
棧區 (Stack) : 局部變數、函數幀
p2[4] 在這裡 (可讀寫)
main() 幀
為何 strcpy 崩潰而 strlen 正常?
| 函數 | 操作 | 對常量區影響 |
| `strcpy(dest, src)` | **寫入** `dest` 指向的記憶體 | 若 `dest` 指向常量區 → **崩潰** |
| `strlen(str)` | **唯讀** 遍歷計算長度 | 常量區可讀 → **正常** |
正確使用模式
// 1. 目標必須是可寫緩衝區 (棧上陣列或 heap)
char dest[20]; // 棧上,可寫
char *src = "hello"; // 常量區,唯讀
strcpy(dest, src); // ✓ 正確
// 2. 動態分配目標
char *dest = malloc(strlen(src) + 1);
strcpy(dest, src);
free(dest);
// 3. 需修改字串內容時,勿用指針指向常量
// 錯誤: char *p = "hello"; p[0] = 'H';
// 正確: char p[] = "hello"; p[0] = 'H'; // 陣列在棧,內容可改
VC6 特殊行為 (歷史背景)
- VC6 不強制執行常量區唯讀 (部分配置下允許寫入),但**屬未定義行為**,換編譯器 (GCC/MSVC 新版) 必崩。
- 現代編譯器 (GCC/Clang/MSVC) 默認將字串常量放入 `.rodata` 段,頁面屬性 `READ_ONLY`,寫入直接觸發硬體異常。
最佳實踐:const 正確性
// 函數參數加 const,編譯器會攔截嘗試修改的代碼
void safe_copy(char *dest, const char *src) {
// strcpy(dest, src); // dest 無 const,可寫
}
// 字串指針聲明
const char *msg = "hello"; // 明確承諾不修改內容
// char *msg = "hello"; // C++11 起棄用,C 標準允許但危險
調試技巧
- 崩潰時查看反組裝/記憶體視圖,確認目標地址屬於哪個段 (`.rodata` vs `.stack`)。
- 啟用編譯器警告 `-Wwrite-strings` (GCC) 或 `/W4` (MSVC),捕獲 `char*` 指向字串常量。
- 使用 `valgrind` / AddressSanitizer (`-fsanitize=address`) 自動檢測非法寫入。