一个数组,无论是一维、二维还是更多,都不指向任何东西。它是在连续内存中的一组相同类型的对象。
在int arr[6][8]; 之后,arr 是一个由 8 个 int 组成的 6 个数组的数组。如果每个int 使用四个字节,那么整个数组在内存中使用 6•8•4 = 192 字节,sizeof arr 将计算为 192,因为arr 是数组,并且sizeof 给出其操作数的大小。
当您在表达式中使用arr 时,它将自动转换为指向其第一个元素的指针,除非它是sizeof 或一元& 的操作数。 1 因为arr是一个数组数组,所以它的第一个元素也是一个数组。 arr的第一个元素是arr[0],是一个8个int的数组。因此,在arr+1 中,arr 被转换为指向其第一个元素的指针。那个指针是&arr[0],arr[0]的地址。
当一个整数,比如 n 被添加到一个指针上时,结果指向数组中另外 n 个元素。因此,当我们将 1 加到 &arr[0] 时,我们得到 &arr[1]。因此,在arr+1中,arr自动转换为&arr[0],产生&arr[0]+1,然后相加产生&arr[1]。
所以arr+1就是&arr[1],这也是arr[1][0]所在的地方。 (请注意,虽然&arr[1] 和&arr[1][0] 指向内存中的同一位置,但它们具有不同的类型,因此编译器在对它们进行算术运算时会以不同的方式处理它们。)
在int *ptr = &arr[0][0];之后,ptr当然指向arr[0][0]。然后ptr+8 指向arr[0][8] 的位置,如果有这样的元素。当然,没有,因为arr[0] 只有从arr[0][0] 到arr[0][7] 的元素。 ptr+8 比arr[0][7] 高一分。
指针算法是由 C 标准定义的;您可以指向“最后一个元素之外的一个”。然而,这只是一个占位符指针,对算术和比较有用。使用 *(ptr+8) 取消引用指针时不会发生 C 标准。
我们知道arr[1][0] 位于假设arr[0][8] 所在的位置。然而,C 标准并没有给我们规定说我们绝对可以使用*(ptr+8) 访问arr[1][0]。因此该代码没有 C 标准定义的行为。不过,许多编译器会认为它可以访问arr[1][0]。
如您所述,*(*(arr+1)) 可用于访问arr[1][0]。它的工作方式是:
-
arr 会自动转换为 &arr[0]。
- 加 1 得到
&arr[1]。
-
* 取消引用 &arr[1],生成 arr[1]。
-
arr[1] 是一个数组,因此它会自动转换为指向其第一个元素的指针。这会产生&arr[1][0]。
-
* 取消引用 &arr[1][0],生成 arr[1][0]。
相比之下,*(*(arr+8)) 无法访问arr[1][0]:
-
arr 会自动转换为 &arr[0]。
- 加 8 得到
&arr[8]。
现在我们已经远远超出了arr 的终点。 arr 仅包含从 arr[0] 到 arr[5] 的元素,因此 arr[8] 超出了结尾。
请注意,将 8 添加到 &arr[0] 会将指针移动 arr 数组的 8 个元素,而不是 arr[0] 数组的 8 个元素。也就是说,它移动了 8 个数组,每组 8 个 int,而不是 8 个 int。这是因为&arr[0] 的type 是“指向8 个数组的指针int”,而不是“指向int 的指针”。
将 8 添加到“指向int”的指针时,会将其移动 8 int。当您将 8 添加到“指向 8 个int 的数组的指针”时,您将其移动了 8 个由 8 个int 组成的数组。
脚注
1 所有数组都是这样自动转换的,字符串字面量还有一个例外。当使用字符串字面量(即字符数组)初始化字符数组时,它不会自动转换为指针。