在 C 語言中,char *p 與 char p[] 這兩種字串定義方式,雖然在高層代碼中看似相似,但在記憶體佈局與底層機制上卻有著本質不同。本文將通過 GDB 反組譯輸出,從組合語言層面揭示兩者的核心差異。
測試代碼如下:
#include <stdlib.h>
#include <stdio.h>
void main() {
char array1[] = { "123456" };
char *pointer1 = "123456";
}
使用 GDB 反組譯 main 函數:
(gdb) disassemble main
Dump of assembler code for function main:
0x08048394 <+0>: push %ebp
0x08048395 <+1>: mov %esp,%ebp
0x08048397 <+3>: sub $0x10,%esp
0x0804839a <+6>: movl $0x34333231,-0xb(%ebp)
0x080483a1 <+13>: movw $0x3635,-0x7(%ebp)
0x080483a7 <+19>: movb $0x0,-0x5(%ebp)
0x080483ab <+23>: movl $0x8048484,-0x4(%ebp)
0x080483b2 <+30>: leave
0x080483b3 <+31>: ret
End of assembler dump.
array1 的記憶體分配觀察指令 <+6>、<+13>、<+19> 三條指令:
movl $0x34333231, -0xb(%ebp) —— 寫入 "1234" (小端序:0x31='1', 0x32='2', 0x33='3', 0x34='4')movw $0x3635, -0x7(%ebp) —— 寫入 "56" (0x35='5', 0x36='6')movb $0x0, -0x5(%ebp) —— 寫入結束符 '\0'程式在堆疊上為 array1 分配了 7 bytes 空間,直接存放字串內容 "123456\0"。數組名 array1 代表這塊記憶體的首地址,內容可修改。
pointer1 的記憶體行為指令 <+23>:movl $0x8048484, -0x4(%ebp) 將地址 0x8048484 存入堆疊變量 pointer1。查看該地址內容:
(gdb) x/7xb 0x8048484
0x8048484: 0x31 0x32 0x33 0x34 0x35 0x36 0x00
字串字面量 "123456" 存放在唯讀資料段,pointer1 僅持有其地址,未在堆疊為字串內容分配空間。嘗試修改 *pointer1 將導致未定義行為(通常是段錯誤)。
擴展代碼增加兩個數組與兩個指標:
char array1[] = { "123456" };
char array2[] = { "123456" };
char *pointer1 = "123456";
char *pointer2 = "123456";
反組譯顯示:
array1 與 array2 各自在堆疊分配獨立的 7 bytes,內容相同但地址不同(指令 <+6>~<+19> 與 <+23>~<+36>)pointer1 與 pointer2 均指向同一地址 0x8048494(指令 <+40>、<+47>),編譯器優化合併了相同字串字面量若指標指向不同字串:
char *pointer1 = "123456";
char *pointer2 = "12345678";
反組譯指令 <+40>、<+47> 分別載入 0x8048494 與 0x804849b,記憶體檢查確認兩地址存放不同字串內容。
| 特性 | char p[] | char *p |
|---|---|---|
| 記憶體分配 | 堆疊分配,含字串副本 | 堆疊僅分配指標變量,字串在唯讀區 |
| 內容可修改性 | 可修改 | 不可修改(UB) |
| 多實例行為 | 每個數組獨立擁有副本 | 相同字面量共享地址(編譯器優化) |
| sizeof 結果 | 字串長度+1 | 指標大小(4/8 bytes) |
從組合語言視角可見,char p[] 是「擁有數據的容器」,char *p 是「指向數據的引用」。理解這一區別,對避免緩衝區溢出、正確處理字串修改、優化記憶體佈局至關重要。