C 并没有真正将多维数组与单维数组区别对待。
C中的数组只是数组的数组
char a[2][3][4][5];
是array 2 of array 3 of array 4 of array 5 of char。
对于任何“T 的数组 A”,取消引用/下标的工作方式相同:
- 将 A 衰减到第一个元素的地址(如果您要延迟/下标指针,则不执行任何操作)
- 添加由
sizeof(T)缩放的索引
在 C 中使用取消引用/下标,当您谈到一个时,您就谈到另一个,因为 A[Index] 或 Index[A] 被定义为与 *(A+Index) 或 *(Index+A) 相同。
6.5.2.1p2
后缀表达式后跟方括号 [] 中的表达式是数组对象元素的下标名称。下标运算符[]的定义是E1[E2]等同于(*((E1)+(E2)))。由于适用于二元 + 运算符的转换规则,如果 E1 是数组对象(等效地,指向数组对象的初始元素的指针)并且 E2 是整数,则 E1[E2] 指定第 E2 个元素E1(从零开始计数)。
由于char a[2][3][4][5]; a 是 数组 2 的(char 数组 5 的数组 4 的数组 3),a[1] 会给你((char*)&a) + 1 * sizeof(char[3][4][5]),结果将有输入char[3][4][5]。
现在数组的特殊之处在于此——数组不是 C 中的一等对象。你不能拥有数组类型的 r 值。当您尝试获取一个时,例如通过将数组传递给函数或运算符,数组立即衰减为指向其第一个元素的指针,因此a[1] 的char[3][4][5] 类型立即更改为char(*)[4][5]。
6.5.2.1p3
连续的下标运算符指定多维数组对象的一个元素。如果 E 是维度为 i x j x 的 n 维数组 (n >= 2)。 . . x k,然后 E(用作左值以外的值)被转换为指向具有维度 j x 的 (n - 1) 维数组的指针。 . . xk。如果一元 * 运算符显式地或作为下标的结果隐式地应用于此指针,则结果是引用的 (n - 1) 维数组,如果用作左值以外的数组,它本身将转换为指针。由此得出,数组以行优先顺序存储(最后一个下标变化最快)。
这会递归地继续,直到您切掉所有维度(从右到左)并留下一个不会衰减的真实类型。实际上,中间数组的衰减意味着中间的 derefs/subscripts 并没有真正获取任何东西——它们只是对基地址的添加。
char a[2][3][4][5]; 的一些示例:
#include <stdio.h>
char a[2][3][4][5];
#define ASSERT_TP(Expr,Tp) _Generic(Expr,Tp: (char*)(Expr))
int main()
{
printf("%zd\n", ASSERT_TP(a,char(*)[3][4][5])
- (char*)a); //0
printf("%zd\n", ASSERT_TP(a[1],char(*)[4][5])
- (char*)a); //60 == 1 * (3*4*5)
printf("%zd\n", ASSERT_TP(a[1][1],char(*)[5])
- (char*)a); //80 == 1 * (3*4*5) + 1 * (4*5)
}
应用于您的示例:
int a[1][2] = {1,2}; // a decays to ptr to 1st element,
//i.e. to `int (*a)[2]`
printf("%p or %p\n",
*(a+0), // == a[0]; ((char*)&a) + 0*sizeof(int[2]);
// type is int[2], which decays to int*
a+0); // == a (after decay); (char*)&a + 0*sizeof(int[2]);
//type is still `int(*)[2]` (because no derefing)
因为*(a+0) 中的 deref 还没有命中真正的类型,所以没有获取,只是对基指针的添加
与类型调整。由于加法添加了 0,因此值没有改变它保持与 a 衰减到指向其第一个元素的指针 (== a+0) 甚至 &a 相同(这将具有相同的数字地址但它的类型是int (*)[1][2])。