C/C++ 初學者常遇到這樣的困惑:char *p = new char[20]; 分配內存後,為什麼 strcpy(p, "helloworld") 正常,而 p = "helloworld" 會導致警告甚至運行時錯誤?本文將從內存模型、指針本質、字符串字面量存儲三個維度徹底講透這個問題。
char *p = new char[20]; // 1. 堆上分配 20 字節,p 指向首地址
strcpy(p, "helloworld"); // 2. 將字面量內容「拷貝」到 p 指向的內存
// 對比
p = "helloworld"; // 3. 讓 p 直接指向字面量所在的只讀區
+------------------+ +------------------+ | Stack (棧) | | Heap (堆) | | p (指針變量) ───┼──────▶│ 20 bytes 空間 | ← new char[20] +------------------+ +------------------+ +------------------+ +------------------+ | Stack | | .rodata (只讀區) | | p (指針變量) ───┼──────▶│ "helloworld\0" | ← 字符串字面量 +------------------+ +------------------+
p[0] = 'H'; 合法。delete[] p; 釋放堆內存,否則洩漏。new char[20] 分配的 20 字節堆內存無指針引用,無法釋放。p[0] = 'H'; 會導致段錯誤/訪問違例。const char* 隱式轉換為 char*,丟棄 const 資格。C++ 標準允許字符串字面量隱式轉換為 char*(為兼容 C 舊代碼),但強烈建議使用 const char* 接收字面量:
const char *p = "helloworld"; // 正確:編譯器強制只讀
// p[0] = 'H'; // 編譯錯誤,保護只讀區
| 場景 | 推薦做法 | 關鍵點 |
|---|---|---|
| 需要可修改的字符串緩衝區 | std::string s = "helloworld"; 或 std::vector<char> | 自動管理內存,避免手動 new/delete |
| 必須用 C 風格 char* 且需修改 | char *p = new char[len]; strcpy(p, src); ... delete[] p; | 配對 new[]/delete[],檢查邊界 |
| 只讀字符串常量 | const char *p = "literal"; | 編譯期保護,零拷貝開銷 |
| C++17 起 | std::string_view sv = "literal"; | 非擁有式視圖,極低開銷 |
除非維護舊代碼或編寫底層庫,優先使用 std::string、std::string_view、std::vector,利用 RAII 自動管理生命週期,從根源消除指針算術、內存洩漏、野指針等經典問題。
strcpy 是「搬運內容」,p = "..." 是「換掉地址」。理解指針變量本身 vs 指向的內存、堆 vs 只讀區、所有權語義,是從 C/C++ 初學者進階為系統級工程師的必經之路。