陣列與指標在 C 語言中常被混為一談,但編譯器產生的組合語言指令卻有本質差異。本文以 VC6.0 Debug 模式輸出為例,逐行註解陣列索引存取 vs 指標間接存取的組合碼差異,並揭示 extern 宣告時「陣列 vs 指標」型別不匹配導致的連結錯誤與執行期崩潰風險。
#include "stdafx.h"
int main(int argc, char* argv[]) {
char a = 1;
char c[] = "1234567890"; // 陣列:堆疊上分配 12 bytes(含結束符)
char *p = "1234567890"; // 指標:指向唯讀資料段字串常值
a = c[1]; // 陣列存取
a = p[1]; // 指標存取
return 0;
}
; char c[] = "1234567890";
0040102C mov eax, [string "1234567890" (0042201c)] ; 讀前 4 bytes "1234"
00401031 mov dword ptr [ebp-10h], eax ; 存入 c[0..3]
00401034 mov ecx, dword ptr [string "1234567890" 4 (00422020)] ; "5678"
0040103A mov dword ptr [ebp-0Ch], ecx ; 存入 c[4..7]
0040103D mov dx, word ptr [string "1234567890" 8 (00422024)] ; "90"
00401044 mov word ptr [ebp-8], dx ; 存入 c[8..9]
00401048 mov al, [string "1234567890" 0Ah (00422026)] ; "\0"
0040104D mov byte ptr [ebp-6], al ; 存入 c[10]
陣列內容被複製到堆疊框架(ebp-10h 起始),佔用 12 bytes。
; char *p = "1234567890";
00401050 mov dword ptr [ebp-14h], offset string "1234567890" (0042201c)
ebp-14h 僅存放字串常值的首地址(4 bytes),不複製內容。
a = c[1]:2 步驟
00401057 mov cl, byte ptr [ebp-0Fh] ; 直接計算位移 ebp-10h+1 → 讀取
0040105A mov byte ptr [ebp-4], cl ; 存入 a
編譯器靜態知道陣列基址(ebp-10h),直接以位移存取,**無間接層級**。
a = p[1]:3 步驟
0040105D mov edx, dword ptr [ebp-14h] ; 1. 取得指標值(地址)
00401060 mov al, byte ptr [edx + 1] ; 2. 間接位址 + 位移讀取
00401063 mov byte ptr [ebp-4], al ; 3. 存入 a
需先載入指標值,再間接定址,**多一條指令**。
| 操作 | 指令數 | 記憶體存取 | 備註 |
|---|---|---|---|
陣列索引 c[i] | 2 | 1 次(直接位移) | 基址為編譯期常數 |
指標索引 p[i] | 3 | 2 次(取指標 + 間接) | 基址為執行期變數 |
在最佳化開啟(Release)下,編譯器可能將指標值保持在暫存器中,差距縮小;但陣列存取永不慢於指標。
// inc.h
char chTest[10] = "123456789";
// test.cpp
#include "inc.h"
extern char chTest[10]; // 型別匹配:char[10]
int main() {
printf("chTest=%s\n", chTest); // 正常
return 0;
}
// test.cpp
extern char *chTest; // 型別不匹配:char* vs char[10]
編譯錯誤:redefinition; different types of indirection
char chTest[10]:符號 chTest 代表陣列首地址(類型 char[10],大小 10)。char *chTest:符號 chTest 代表指標變數(類型 char*,大小 4/8),其內容儲存地址。
// 陣列:基址 + 位移
mov al, byte ptr [chTest + 1]
// 指標:載入指標值 + 間接
mov eax, dword ptr [chTest]
mov al, byte ptr [eax + 1]
若強制連結通過(如關閉型別檢查),執行時會將陣列首地址當作指標值解讀,導致野指標存取、記憶體損毀、程式崩潰。
char buf[100] 而非 char *buf = malloc(100)(除非需動態大小)。const char *const STR = "...",extern 同型別。-Wall -Wextra -pedantic(GCC/Clang)或 /W4(MSVC)可捕獲型別不匹配。陣列與指標在組合語言層面的差異,揭示了 C 語言「陣列非指標」的本質:陣列是連續記憶體區塊,指標是儲存地址的變數。理解這一點,不僅能寫出更高效的程式碼,更能避免 extern 型別不匹配這類隱蔽且嚴重的錯誤。