【问题标题】:Are two dimensional arrays in C required to make all elements contiguous?C中的二维数组是否需要使所有元素连续?
【发布时间】:2013-06-12 02:31:55
【问题描述】:

从朋友那里听说,C 中的二维数组仅在语法上受支持。

他告诉我最好使用float arr[M * N] 而不是float[M][N],因为像 gcc 这样的 C 编译器不能保证在每个系统/平台上,数据都在内存中串联。

我想在我的硕士论文中使用它作为论据,但我没有任何参考。

所以第一个问题:

他说的对吗?

第二个问题:

你知道在哪里可以找到这句话的书或文章吗?

谢谢+问候

【问题讨论】:

  • Kernighan 和 Ritche 合着的 C Programming Language 是最好的书。看看吧
  • K&R 不错,但一年比一年过时。最后一次修订是在 1988 年。
  • @JackAidley,标准没有说明物理内存。我认为这就是丹尼尔试图达到的目的。
  • @DanielFischer:啊,好吧。我同意你的观点。不过,对程序员来说,逻辑连续性很重要。
  • @DanielFischer 如果您指的是 MMU 的虚拟物理映射,这在 C 中完全不相关,因为在 C 中无法检测到。数组和数组的数组在内存中是连续的,即地址增加没有间隙,并且 sizeof(x[N][M]) == sizeof (x[N*M])。

标签: c arrays


【解决方案1】:
  1. 不,他错了。

  2. 查看 C 标准。一些相关位(我的粗体强调):

    6.2.5 类型¶20

    array 类型描述了具有特定成员对象类型的连续分配非空对象集,称为元素类型

    6.7.6.2 数组声明器¶3(注释 142)

    当多个“数组”规范相邻时,声明一个多维数组。

    6.5.2.1 数组下标¶3

    连续的下标运算符指定多维数组对象的一个​​元素。 ...由此可知数组以行优先顺序存储(最后一个下标变化最快)。

    也许最明确的是,6.5.2.1 数组下标¶4中的示例:

    示例考虑由声明定义的数组对象

    int x[3][5];

    这里xints的3×5数组;更准确地说,x 是一个由三个元素对象组成的数组,每个对象是一个由五个ints 组成的数组。在表达式x[i](相当于(*((x)+(i))))中,x 首先被转换为指向五个ints 的初始数组的指针。然后根据x的类型调整i,这在概念上需要将i乘以指针指向的对象的大小,即五个int对象的数组 .结果被添加并应用间接以产生一个由五个ints 组成的数组。当在表达式x[i][j] 中使用时,该数组又被转换为指向第一个整数的指针,因此x[i][j] 产生一个整数。

C 中的多维数组只是“数组的数组”。它们工作正常,并且 100% 由标准定义。

阅读 comp.lang.c 常见问题解答中的 Section 6, Arrays and Pointers 也会对您有所帮助。

【讨论】:

  • 所以 C 标准说编译器应该连续存储数组。但是他们都遵循这个规则吗?这可能是他朋友说的。我不知道答案,只是好奇。
  • 我从来没有听说过其他的。此外,如果没有,那它就不是 C 编译器了,对吧?
【解决方案2】:

这个问题比其他答案听起来更微妙:

虽然多维数组(在语义上,可能不是物理上)是连续的,但只有在指针最初引用的数组边界内(实际上,您可以超过上限 1 个元素,但仅当您不取消引用时)。

这意味着语言语义禁止从头到尾遍历多维数组,而 C 语言的边界检查实现(原则上是可能的,但出于性能原因很少在野外看到)可能会引发segfault、打印诊断信息或在您越过子阵列的边界时让恶魔从您的鼻子上飞走。

我不确定编译器是否将这些信息用于优化目的,但原则上可以。例如,如果您有

float *p = &arr[2][3];
float *q = &arr[5][9];

那么p + xq + y 不应该使用别名,无论xy 的值如何。

【讨论】:

【解决方案3】:

第 6.2.5.20 节要求数组是连续分配的。这同样适用于数组数组和一维数组。

你的朋友完全错了。

【讨论】:

    【解决方案4】:

    C 中内置的多维数组是通过索引转换实现的。这意味着,例如,3D 数组 T a[M][N][K] 被实现为 1D 数组 T a_impl[M * N * K],而多维访问 a[i][j][k] 被隐式转换为单维访问 a_impl[((i * N) + j) * K + k]。语言规范没有明确描述此实现,但需求几乎直接要求它。

    考虑到这一点,不清楚为什么您的朋友会告诉您显式使用float arr[M * N],而不是依赖编译器对同一事物的隐式实现。

    MN 都是运行时值并且你的编译器不支持可变长度数组(或者你出于某种原因不想使用它们)。在这种情况下,对多维数组的内置支持不再适用,因为它依赖于所有大小(第一个除外)都是编译时常量。也许这就是你朋友的想法。

    【讨论】:

    • 即使你的编译器不支持 VLA,你仍然可以在运行时分配一个真正的多维数组。使用它只是一些(粗略的)语法体操。
    • @Carl Norum:如果第二个、第三个和更多大小是编译时常量,则可以分配一个真正的多维数组。 (如上式所示,第一个大小M不参与索引重新计算)。这就是为什么我说当 both M N 都是运行时值时,必须考虑“手动”实现。
    • 还有一个问题是,如果不将多维数组的维度硬编码在接受它的函数的签名中,就无法传递多维数组。而且,当然,这样做是邪恶的。所以,最好听你的朋友的话,自己做指针算术。
    • @cmaster:无需硬编码维度 - C99 具有可变修改类型
    猜你喜欢
    • 2018-07-28
    • 1970-01-01
    • 2019-07-04
    • 1970-01-01
    • 2016-08-07
    • 2022-01-25
    • 1970-01-01
    • 1970-01-01
    • 2013-05-16
    相关资源
    最近更新 更多