許多 C 語言初學者在面對二維數組時,最頭疼的莫過於「數組名到底代表什麼?」以及「行指針與列指針有何區別?」。本文將從一維數組類比入手,層層剖析二維數組的本質、指針等價關係、記憶體佈局,並結合 memset 實例說明指針語義在實際編程中的影響。
對於一維數組 int a[3] = {1,2,3};,數組名 a 等價於 &a[0],兩者皆為首元素地址(列地址)。數組訪問本質上是指針運算:
a[i] 等價於 *(a + i)
&a[i] 等價於 (a + i)
指針運算的步長由元素類型決定,int* 步長為 sizeof(int)(通常 4 位元組)。
C 語言並無原生二維數組,int a[3][4] 實為「包含 3 個一維數組(每個長度 4)的一維數組」。即 a 是一個含 3 個元素的一維數組,每個元素 a[i] 又是一個含 4 個 int 的一維數組。
&a[0],類型為 int (*)[4](指向含 4 個 int 的數組的指針),代表行地址,步長為一整行大小(4 * sizeof(int) = 16 位元組)。&a[0][0],類型為 int*,代表列地址,步長為單個 int 大小(4 位元組)。雖然 a、a[0]、&a[0]、&a[0][0] 的數值(位址)相同,但它們的類型與語義截然不同,直接影響指針運算的步長。
int b[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
printf("%u\n", b+1); // +16 位元組(跳過一整行)
printf("%u\n", b[0]+1); // +4 位元組 (跳過一個 int)
printf("%u\n", &b[0]+1); // +16 位元組(同 b+1,行地址)
這正是「值相同,義不同」的核心體現:指針類型決定運算步長。
初學者常困惑:memset 該傳 a、a[0]、&a[0] 還是 &a[0][0]?它們位址相同,卻類型不同。
void *memset(void *dest, int c, size_t count); 接受 void*,意即僅關心起始位址與字節數,完全忽略類型語義。memset 內部以字節(char*)為單位逐字節填充,因此傳入上述任一指針皆可,只要 count 正確(如 sizeof(a))。
這也是為何 C 標準庫用 void* 實現泛型記憶體操作:擦除類型,只留地址與位元組計數。
a(行指針)步長 = 一行大小;a[0](列指針)步長 = 一元素大小。void* 擦除類型,適用於 memset、memcpy 等純記憶體操作。理解這層「類型決定步長」的機制,二維數組指針難題便迎刃而解。