C 語言中單引號表示字符,雙引號表示字符串,二者本質截然不同。誤用會導致詭異 Bug。本文通過實驗代碼、內存視圖、編譯器行為詳細對比兩者本質。
實驗 1:字符常量特性
char ch_1 = 'a';
char ch_2 = 'abcd'; // 警告:截斷
int ch_val_1 = 'abcdef'; // 錯誤:超過 4 字節
int ch_val_2 = 'abcd'; // 合法
VS2010 編譯結果
| 變量 | 值(十六進制) | 說明 |
| ch_1 | 0x61 | 'a' 的 ASCII |
| ch_2 | 0x64 | 取最後一字符 'd' |
| ch_val_2 | 0x61626364 | 'a''b''c''d' 組合 |
| ch_val_1('abc') | 0x00616263 | 高位補 0 |
核心結論
- 字符常量本質是整數,值為 ASCII 碼組合
- 多字符常量
'abcd' 非標準,編譯器行為不一(VC 取尾,GCC 可能警告/錯誤)
- 超過
int 寬度(4 字節)會報錯,不會自動截斷
實驗 2:字符串常量特性
char *p1 = "testchar";
char *p2 = (char*)0x74657374; // 手造指針
char *p3 = "testchar";
char *p4 = "char"; // 子串
關鍵觀察
p1 == p3:編譯器合併相同字面量(唯讀段優化)
p1 != p4:子串不共享地址,因無長度資訊無法切片
- 修改
*p1 = 'X' → 運行時崩潰(段錯誤),字面量在 .rodata 唯讀段
printf(p1) 正常:傳入首地址,遇 \0 停止
本質區別總結表
| 特性 | 單引號 'a' | 雙引號 "a" |
| 本質 | 整數常量 | 指向無名陣列的指針 |
| 大小 | sizeof(int) | 字符數+1 (含\0) |
| 存儲區 | 立即數/指令中 | .rodata 唯讀數據段 |
| 可修改 | N/A (非左值) | 不可修改 (運行時崩潰) |
| 相同內容合併 | 無此概念 | 編譯器優化合併 |
| 類型 | int (C89) / char (C++) | char* / const char* |
常見陷阱與避坑
// 陷阱 1:字符 vs 字符串作參數
void foo(char c); void bar(char *s);
foo('a'); // 正確
foo("a"); // 警告:指針轉整數
bar("a"); // 正確
bar('a'); // 錯誤:整數轉指針
// 陷阱 2:字符串字面量修改
char *s = "hello";
s[0] = 'H'; // 未定義行為,通常崩潰
// 正確:
char s[] = "hello"; // 棧上陣列,可修改
s[0] = 'H'; // OK
// 陷阱 3:sizeof 差異
sizeof('a') // 4 (int)
sizeof("a") // 2 (含\0)
最佳實踐
- ✅ 字符字面量用
'a',字符串用 "a"
- ✅ 需修改字符串時,用陣列
char s[] = "..."
- ✅ 函數參數用
const char* 接收字面量
- ✅ 不要比較字符串指針
p1 == p2,用 strcmp
- ✅ 理解
'abcd' 非標準,移植性差,避免使用