C99是否保证arrays是连续的?

在另一个问题的热门评论主题之后,我开始讨论C99标准中有关C数组的内容和内容。

基本上当我定义一个2D数组如int a[5][5] ,标准C99是否保证它将是一个连续的整数块,我可以将其转换为(int *)a并确保我将拥有一个有效的一维25个整数数组。

正如我理解标准的那样,上面的属性隐含在sizeof定义和指针算术中,但是其他人似乎不同意并且说要转换为(int *)上面的结构给出了一个未定义的行为(即使他们同意所有现有的实现实际分配连续的价值观)。

更具体地说,如果我们认为一种实现可以检测数组检查所有维度的数组边界并在访问1D数组时返回某种错误,或者不能正确访问第1行以上的元素。 这样的实施可以是标准的编译吗? 在这种情况下,C99标准的哪些部分是相关的。

我们应该从检查[5] [5]究竟是什么开始。 涉及的类型是:

  • INT
  • 整数[5]的整数
  • arrays的数组[5]

没有涉及整数[25]的整数。

正确的语义大小意味着整个数组是连续的。 int的数组[5]必须具有5 * sizeof(int),并且递归地应用,[5] [5]必须具有5 * 5 * sizeof(int)。 没有额外填充的空间。

此外,当给出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]。 在Christoph的回答中使用联合也应该是有效的。 但是还有另一个问题,即平等运算符:

6.5.9.6两个指针比较相等,当且仅当两个都是空指针时,两者都是指向同一对象的指针(包括指向对象的指针和开头的子对象)或函数,两者都指向超过最后一个元素的指针。相同的数组对象, 或者一个是指向一个数组对象末尾的指针,另一个是指向不同数组对象的开头的指针,该数组对象恰好跟随地址空间中的第一个数组对象。 91)

91)两个对象可能在内存中相邻,因为它们是较大数组的相邻元素或结构的相邻成员,它们之间没有填充,或者因为实现选择放置它们,即使它们是不相关的。 如果先前的无效指针操作(例如数组边界外的访问)产生了未定义的行为,则后续比较也会产生未定义的行为。

这意味着:

 int a[5][5], *i1, *i2; i1 = &a[0][0] + 5; i2 = &a[1][0]; 

i1与i2相等。 但是当使用(int *)迭代数组时,它仍然是未定义的行为,因为它最初是从第一个子数组派生的。 它不会神奇地转换为指向第二个子arrays的指针。

即使这样做

 char *c = (char *)(&a[0][0]) + 5*sizeof(int); int *i3 = (int *)c; 

没有用。 它比较等于i1和i2,但它不是从任何子arrays导出的; 它是指向单个int或int的数组[1]的指针。

我不认为这是标准中的错误。 反过来说:允许这样会引入一种特殊情况,它违反了数组的类型系统或指针算法的规则或两者。 它可能被认为是缺少定义,但不是错误。

因此,即使[5] [5]的内存布局与[25]的布局相同,并且使用(char *)的相同循环也可以用于迭代两者,允许实现吹如果一个被用作另一个。 我不知道为什么它应该或知道任何实现,并且可能在标准中没有提到的任何事实直到现在才使其成为明确定义的行为。 在那之前,我认为它是不确定的并保持安全。

我在原始讨论中添加了一些评论。

sizeof语义暗示int a[5][5]是连续的,但是通过递增指针来访问所有25个整数,如int *p = *a是未定义的行为:指针算术仅在所有指针都被置于其中时定义(或者一个元素超过同一个数组的最后一个元素,例如&a[2][1]&a[3][1]不会(参见C99第6.5.6节)。

原则上,您可以通过将类型为int (*)[5][5]int (*)[25] 。 根据6.3.2.3§7这是合法的,因为它没有违反任何对齐要求。 问题是通过这个新指针访问整数是违法的,因为它违反了6.5§7中的别名规则。 你可以通过使用一个用于类型惩罚的union来解决这个问题(参见TC3中的脚注82):

 int *p = ((union { int multi[5][5]; int flat[25]; } *)&a)->flat; 

据我所知,这是符合标准的C99。

如果数组是静态的,就像你的int a[5][5]数组一样,它保证是连续的。