图片可能会有所帮助。为了篇幅,我们假设x 和y 是2。给定声明
int arr[2][2];
我们在内存中得到这个:
int
+–––+
arr: | | arr[0][0]
+–––+
| | arr[0][1]
+–––+
| | arr[1][0]
+–—-+
| | arr[1][1]
+–––+
请注意,没有为任何指针留出空间 - 没有对象 arr 与数组元素本身分开。
代码
int **arr = malloc( 2 * sizeof *arr );
for ( size_t i = 0; i < 2; i++ )
arr[i] = malloc( 2 * sizeof *arr[i] );
我们明白了:
int ** int * int
+–––+ +–––+ +–––+
arr: | | -–> | | arr[0] ––––> | | arr[0][0]
+–––+ +–––+ +–––+
| | arr[1] ––+ | | arr[0][1]
+–––+ | +–––+
|
| +–––+
+–> | | arr[1][0]
+–––+
| | arr[1][1]
+–––+
在这种情况下,您有 三个 指针 - arr 指向一个 指针序列,每个指针都指向一个 int 序列。
那么,arr[x][y] 是如何评估每个的?
请记住,表达式a[i]定义为*(a + i) - 给定地址a,偏移i 元素(不是字节!)地址并取消引用结果。
arr[i][j] == *(arr[i] + j) == *(*(arr + i) + j)
如果arr 是int 的二维数组或指向int 的指针,则计算结果完全相同。
在第二种情况下,事情很明显——我们正在处理一堆显式指针。 arr 显式存储了arr[0] 的地址,arr[0] 显式存储了arr[0][0] 的地址等。所以arr[i] == *(arr + i) 和arr[i][j] == *(*(arr + i) + j) 完全符合逻辑。
但是第一种情况呢?没有指针明确存储在任何地方。 arr 不存储arr[0] 的地址(没有单独的arr[0],这意味着没有任何东西可以存储arr[0][0] 的地址)。那么arr[i][j]如何评价为*(*(arr + i) + j)呢?
像这样 - 除非它是 sizeof 或一元 & 运算符的操作数,或者是用于初始化 char 数组的字符串文字,表达式类型为 " “T”的 N 元素数组将被转换或“衰减”为“指向 T”的指针类型的表达式,表达式的值将是数组第一个元素的地址。
当编译器在您的代码中看到 表达式 arr 时,除非该表达式是 sizeof 或一元 & 的操作数,它会替换该表达式带有一个指针,该指针的值是数组第一个元素的地址。类似地,表达式arr[i]也被替换为一个指针,那个指针值就是arr[i][0]的地址。请注意,在这种情况下,arr 衰减为类型“指向 int 的 2 元素数组 的指针”(int (*)[2]),而不是“指向 int 的指针的指针”。
Expression Type Decays to Value
---------- ---- --------- -----
arr int [2][2] int (*)[2] Same as &arr[0]
*arr int [2] int * Same as arr[0]
arr[i] int [2] int * Same as &arr[i][0]
*arr[i] int n/a Same as arr[0][0]
arr[i][j] int n/a
&arr int (*)[2][2] n/a Address of array object
&arr[i] int (*)[2] n/a Address of the i'th subarray
所以我们可以认为arr[i][j] 是这样评估的:
*(*(&arr[0] + i) + j)
数组的地址与其第一个元素的地址相同 - 表达式 &arr、arr、&arr[0]、arr[0] 和 &arr[0][0] 都产生相同的地址值,但表达式的类型不同 - int (*)[2][2]、int (*)[2]、int (*)[2]、int [2] => int * 和 int *(这可能会影响指针值的表示方式 - @987654378 可能@ 具有与 int * 不同的表示形式,尽管在您可能遇到的任何系统上都不是这种情况)。
记住指针算法的工作原理——如果p 指向T 类型的对象,那么p + 1 会产生该类型的下一个对象 的地址。如果arr 指向int 的2 元素数组,则arr + 1 产生int 的下一个2 元素数组 的地址。回到我们的第一张图片,但现在有一些额外的表达方式:
int int (*)[2][2] int (*)[2] int *
+–––+ ------------- ---------- -----
| | arr[0][0] <-- &arr arr *arr + 0 (arr[0] + 0)
+–––+
| | arr[0][1] <-- *arr + 1 (arr[0] + 1)
+–––+
| | arr[1][0] <-- arr + 1 *(arr + 1) + 0 (arr[1] + 0)
+–—-+
| | arr[1][1] <-- *(arr + 1) + 1 (arr[1] + 1)
+–––+
同样,每当编译器在表达式中看到 arr 时,它就会将其替换为 &arr[0] 的值,并使用指针算法进行下标。