int arr[3][4] 在內存中是連續 12 個 int,但涉及 arr、arr[0]、*arr、arr+1、*(arr+1) 等多種表達式時,類型與含義截然不同。本文通過實測地址輸出、圖解內存佈局、類型推導三步法,徹底搞懂二維數組指針。
#include <stdio.h>
int main() {
int arr[3][4] = {{11,12,13,14},{21,22,23,24},{31,32,33,34}};
int i, j;
// 打印數組內容
for (i=0;i<3;i++) {
for (j=0;j<4;j++) printf("%d ", arr[i][j]);
printf("\n");
}
// 關鍵地址打印(假設 32 位系統,地址為負數屬正常補碼顯示)
printf("arr = %p\n", (void*)arr); // 數組首地址
printf("&arr[0][1] = %p\n", (void*)&arr[0][1]); // 第0行第1列
printf("arr[0] = %p\n", (void*)arr[0]); // 第0行首地址
printf("arr+1 = %p\n", (void*)(arr+1)); // 第1行首地址(跨過 4 個 int)
printf("*(arr+1) = %p\n", (void*)*(arr+1)); // 同 arr[1],第1行首地址
printf("*(*(arr+1)) = %d\n", *(*(arr+1))); // arr[1][0] = 21
printf("*(*(arr+1)+1) = %d\n", *(*(arr+1)+1)); // arr[1][1] = 22
return 0;
}
11 12 13 14
21 22 23 24
31 32 33 34
arr = 0x7ffee4a0
&arr[0][1] = 0x7ffee4a4
arr[0] = 0x7ffee4a0
arr+1 = 0x7ffee4b0 // +16 bytes = 4 * sizeof(int)
*(arr+1) = 0x7ffee4b0
*(*(arr+1)) = 21
*(*(arr+1)+1) = 22
地址遞增 → +------+------+------+------+------+------+------+------+------+------+------+------+ | 11 | 12 | 13 | 14 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | +------+------+------+------+------+------+------+------+------+------+------+------+ arr[0][0] arr[1][0] arr[2][0] arr (int(*)[4]) arr+1 (int(*)[4]) arr+2 arr[0] (int*) arr[1] (int*) arr[2] (int*)
| 表達式 | 類型 | 含義 | 地址值 |
|---|---|---|---|
arr | int (*)[4] | 指向含 4 個 int 陣列的指針(行指針) | 首地址 |
arr[0] / *arr | int* | 第 0 行首元素地址(列指針) | 首地址 |
arr + i | int (*)[4] | 指向第 i 行的行指針 | 基址 + i * 4 * sizeof(int) |
*(arr + i) / arr[i] | int* | 第 i 行首元素地址 | 基址 + i * 4 * sizeof(int) |
*(arr + i) + j / arr[i] + j | int* | 第 i 行第 j 列元素地址 | 基址 + (i*4+j)*sizeof(int) |
*(*(arr + i) + j) / arr[i][j] | int | 第 i 行第 j 列元素值 | — |
arr + i == &arr[i](類型 int(*)[4])*(arr + i) + j == arr[i] + j == &arr[i][j](類型 int*)*(*(arr + i) + j) == *(arr[i] + j) == arr[i][j]
void foo(int **p) { ... } // int** 不能接收 int[3][4]
foo(arr); // 編譯錯誤/警告
// 1. 明確列數(最常用)
void foo(int arr[][4], int rows) { ... }
void foo(int (*arr)[4], int rows) { ... } // 等價
// 2. 一維扁平化(通用,無列數限制)
void foo(int *arr, int rows, int cols) {
arr[i*cols + j] = ...;
}
// 3. C99 VLA(列數動態)
void foo(int rows, int cols, int arr[rows][cols]) { ... }
數組名 arr 是「行指針」,arr+1 跨過一整行;
解引用一次 *(arr+i) 變「列指針」,指向行首;
再加列偏移 +j 取元素地址,再解引用取值。
函數傳參必須保留列數,或扁平化為一維指針。