19

在另一个问题的热门评论线程之后,我开始讨论 C99 标准中关于 C 数组的定义和未定义的内容。

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

据我了解,上述属性隐含在 sizeof 定义和指针算术中,但其他人似乎不同意并说强制转换为 (int*) 上述结构会产生未定义的行为(即使他们同意所有现有实现实际上都分配了连续值)。

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

4

3 回答 3

18

我们应该首先检查 int a[5][5] 到底是什么。涉及的类型有:

  • 整数
  • 整数数组[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 *) 的相同循环可用于迭代两者,也允许实现如果一个被用作另一个。我不知道它为什么应该或知道任何实现,也许标准中有一个事实直到现在还没有提到,这使它成为定义明确的行为。在那之前,我会认为它是未定义的并保持安全。

于 2010-05-14T13:02:33.923 回答
12

我在我们最初的讨论中添加了更多评论。

sizeof语义意味着它int a[5][5]是连续的,但是通过增加指针来访问所有 25 个整数就像int *p = *a是未定义的行为:指针算术仅在所有涉及的指针位于同一数组内(或最后一个元素之后的一个元素)时才定义,例如&a[2][1]&a[3][1]不要(参见 C99 第 6.5.6 节)。

原则上,您可以通过将&a- 类型转换int (*)[5][5]为 来解决此问题int (*)[25]。根据 6.3.2.3 §7,这是合法的,因为它不违反任何对齐要求。问题是通过这个新指针访问整数是非法的,因为它违反了 6.5 §7 中的别名规则。您可以通过使用 for 类型双关语来解决此问题union(参见 TC3 中的脚注 82):

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

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

于 2010-05-14T09:28:46.433 回答
2

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

于 2010-05-14T09:19:08.450 回答