我们应该首先检查 int a[5][5] 到底是什么。涉及的类型有:
不涉及整数数组[25]。
sizeof 语义暗示整个数组是连续的,这是正确的。int 的数组[5] 必须有 5*sizeof(int),并且递归应用,a[5][5] 必须有 5*5*sizeof(int)。没有额外的填充空间。
此外,当使用 sizeof 提供给 memset、memmove 或 memcpy 时,整个数组必须正常工作。还必须可以使用 (char *) 遍历整个数组。所以一个有效的迭代是:
int a[5][5], i, *pi;
char *pc;
pc = (char *)(&a[0][0]);
for (i = 0; i < 25; i++)
{
pi = (int *)pc;
DoSomething(pi);
pc += sizeof(int);
}
对 (int *) 执行相同操作将是未定义的行为,因为如前所述,不涉及 int 数组 [25]。在克里斯托夫的回答中使用联合也应该是有效的。但是还有另一点使这进一步复杂化,相等运算符:
6.5.9.6 两个指针比较相等当且仅当它们都是空指针,它们都是指向同一个对象(包括指向对象的指针和在其开头的子对象)或函数的指针,两者都是指向最后一个元素的指针相同的数组对象,或者一个是指向一个数组对象末尾的指针,另一个是指向另一个数组对象的开头的指针,该数组对象恰好紧跟在地址空间中的第一个数组对象之后。91)
91) 两个对象在内存中可能是相邻的,因为它们是较大数组的相邻元素或结构的相邻成员,它们之间没有填充,或者因为实现选择这样放置它们,即使它们不相关。如果先前的无效指针操作(例如访问数组边界外)产生未定义的行为,则后续比较也会产生未定义的行为。
这意味着:
int a[5][5], *i1, *i2;
i1 = &a[0][0] + 5;
i2 = &a[1][0];
i1 比较等于 i2。但是当使用 (int *) 遍历数组时,它仍然是未定义的行为,因为它最初是从第一个子数组派生的。它不会神奇地转换为指向第二个子数组的指针。
即使这样做
char *c = (char *)(&a[0][0]) + 5*sizeof(int);
int *i3 = (int *)c;
不会有帮助的。它比较等于 i1 和 i2,但它不是从任何子数组派生的;它最多是指向单个 int 或 int 数组 [1] 的指针。
我不认为这是标准中的错误。反之亦然:允许这样做会引入一种违反数组类型系统或指针算术规则或两者兼而有之的特殊情况。它可能被认为是缺少定义,但不是错误。
因此,即使 a[5][5] 的内存布局与 a[25] 的布局相同,并且使用 (char *) 的相同循环可用于迭代两者,也允许实现如果一个被用作另一个。我不知道它为什么应该或知道任何实现,也许标准中有一个事实直到现在还没有提到,这使它成为定义明确的行为。在那之前,我会认为它是未定义的并保持安全。