char* c1 = "abc";
c1[0] = 'x'; // 執行期崩潰:Access Violation
char c2[] = "abc";
c2[0] = 'x'; // 正常執行,c2 變為 "xbc"
為什麼長得差不多,行為卻天差地別?關鍵在於 **字串字面值存放區** 與 **棧上陣列** 的記憶體屬性差異。本文從編譯器記憶體佈局、執行期行為、組合語言層面深度剖析。
| 區域 | 別名 | 存放內容 | 讀寫屬性 | 生命週期 | 管理方式 |
|---|---|---|---|---|---|
| 程式碼區 | .text | 機器指令、常數字面值 | 唯讀 | 進程存續 | OS 載入器 |
| 資料區 | .data / .bss | 全域/靜態變數 | 讀寫 | 進程存續 | 編譯器配置 |
| 堆疊 | Stack | 區域變數、函式參數、返回位址 | 讀寫 | 函式呼叫期間 | 編譯器自動 (LIFO) |
| 堆積 | Heap | 動態配置記憶體 | 讀寫 | 程式設計師控制 | malloc/free, new/delete |
| 常數區 | .rdata / .rodata | 字串字面值、const 全域變數 | **唯讀** | 進程存續 | 編譯器配置,OS 標記頁面保護 |
記憶體圖示: 常數區 (.rdata) 棧區 (Stack) +------------------+ +------------------+ | 'a' 'b' 'c' '\0' |←─────│ c1 (指標變數) │ 地址值:0x0040xxxx (常數區地址) +------------------+ +------------------+ 地址:0x00403000 地址:0x0012FF7C
"abc" 是 **字串字面值**,編譯期存入唯讀區 .rdata。c1 是棧上指標變數,儲存字面值地址。c1[0] = 寫入唯讀頁面 → CPU 觸發頁面保護錯誤 → OS 送 SIGSEGV/Access Violation。**記憶體圖示: 棧區 (Stack) +------------------+------------------+ | c2[0]='a' │ c2[1]='b' │ c2[2]='c' │ c2[3]='\0' │ +------------------+------------------+ 地址:0x0012FF70
c2 是 **棧上陣列**,大小固定 4 bytes (含結束符)。"abc" 從唯讀區 **複製** 到棧上陣列。sizeof(c2) == 4,&c2 == &c2[0] (陣列名即首元素地址)。char* c3 = (char*)malloc(4);
strcpy(c3, "abc"); // 正確:複製到可寫堆積記憶體
c3[0] = 'x'; // 正常
// 錯誤寫法:
char* c3 = (char*)malloc(4);
c3 = "abc"; // 1. 記憶體洩漏 (原 malloc 區塊丟失)
// 2. c3 指向唯讀區,後續修改崩潰
; char c[] = "abc";
; a = c[1];
mov cl, byte ptr [ebp-0Fh] ; 直接從堆疊相對位址讀取
mov byte ptr [ebp-4], cl
; char *p = "abc";
; a = p[1];
mov edx, dword ptr [ebp-14h] ; 先載入指標值 (常數區地址)
mov al, byte ptr [edx+1] ; 再間接尋址讀取
mov byte ptr [ebp-4], al
結論:陣列直接索引 少一條指令,無指標間接尋址,存取速度略快。
// 錯誤
void UpperCase(char* s) { while(*s) *s++ = toupper(*s); }
UpperCase("hello"); // 崩潰!
// 正確:呼叫端確保傳入可寫緩衝區
char buf[] = "hello";
UpperCase(buf);
char* GetString() {
char local[] = "abc";
return local; // 錯誤:回傳棧上地址,函式結束後失效
}
char* p = "abc";
char a[] = "abc";
sizeof(p); // 4/8 (指標大小)
sizeof(a); // 4 (陣列大小,含 \0)
strlen(p); // 3
strlen(a); // 3
const char* 或 const char[],編譯器會攔截修改嘗試。
const char* msg = "Read-only"; // 好習慣
// msg[0] = 'x'; // 編譯錯誤:cannot modify const object
char[]、std::vector<char>、std::string、std::unique_ptr<char[]>。
const char* 或 std::string_view (C++17)char* buffer, size_t size 或 std::string&std::string/std::string_view:自動管理記憶體、支援 SSO (Small String Optimization)、避免手動記憶體錯誤。
| 特性 | char* = "abc" | char[] = "abc" | malloc + strcpy |
|---|---|---|---|
| 字元存放區 | 唯讀區 (.rdata) | 棧區 (Stack) | 堆積區 (Heap) |
| 可修改性 | **不可修改** (崩潰) | 可修改 | 可修改 |
| 變數大小 | 指標大小 (4/8 bytes) | 陣列長度 (含 \0) | 指標大小 |
| sizeof 結果 | 4/8 | N (含 \0) | 4/8 |
| 生命週期 | 程式全期 | 所在作用域 | 手動管理 |
| 初始化開銷 | 無 (僅指標賦值) | 執行期複製 | malloc + 複製 |
| 適用場景 | 只讀字串、API 傳入 | 本地可修改緩衝區 | 動態大小、跨函式生命週期 |
char* 指向字串字面值是 **唯讀指標**,char[] 是 **可修改的棧上陣列**。理解記憶體分區與生命週期,是避免 C/C++ 低階記憶體錯誤的關鍵。現代開發請善用 std::string/string_view,將裸指標限制在介面邊界與效能熱點。