代码之家  ›  专栏  ›  技术社区  ›  kriss

c99是否保证数组是连续的?

  •  16
  • kriss  · 技术社区  · 14 年前

    在另一个问题的一个热评论线程之后,我开始讨论什么是C数组,什么是C99标准中没有定义的。

    基本上,当我定义一个二维数组 int a[5][5] ,标准的c99 garantee是否将是一个连续的int块,我可以将其强制转换为 (int *)a 确保我有一个25个整数的有效一维数组。

    正如我理解的标准,上述属性在sizeof定义和指针算术中是隐式的,但其他人似乎不同意,并说强制转换(int*)上述结构会给出未定义的行为(即使他们同意 全部的 现有的实现实际上分配连续的值)。

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

    3 回复  |  直到 14 年前
        1
  •  18
  •   Secure    14 年前

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

    • int
    • 整数数组[5]
    • 数组[5]的数组

    不涉及整数数组[25]。

    sizeof语义暗示数组作为一个整体是连续的,这是正确的。Ints的数组[5]必须具有5*sizeof(int),并且递归应用时,[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]。在克里斯托夫的回答中使用联合也是有效的。但还有另一个更为复杂的点,相等运算符:

    5.5.9 两个指针比较相等,如果且仅当两个指针都为空指针时,这两个指针都是指向同一对象(包括指向对象的指针和位于其开头的子对象)或函数的指针,这两个指针都是指向同一数组对象的最后一个元素的指针, 或者一个是指向一个数组对象末尾的一个指针,另一个是指向另一个数组对象的开头的指针,该对象恰好紧跟在地址空间中的第一个数组对象之后。 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]的指针。

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

    因此,即使[5][5]的内存布局与[25]的布局相同,并且可以使用(char*)非常相同的循环对两者进行迭代,如果一个循环用作另一个循环,则允许一个实现爆炸。我不知道它为什么应该或知道任何实现,可能在标准中有一个直到现在才提到的事实,这使得它的行为得到了很好的定义。在那之前,我会认为它是未定义的,并保持在安全的一边。

        2
  •  11
  •   Community Egal    7 年前

    我在我们的 original discussion .

    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中的别名规则。您可以使用 union 对于punning类型(见TC3脚注82):

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

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

        3
  •  2
  •   ThiefMaster    14 年前

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