【问题标题】:Why is char[][] = {{...}, {...}} not possible if explicitly given a multidimensional array?如果明确给出多维数组,为什么 char[][] = {{...}, {...}} 不可能?
【发布时间】:2018-07-29 14:43:03
【问题描述】:

我浏览了this 的文章。我理解解释的规则,但我想知道在定义常量多维数组并使用给定类型的已知值直接初始化它时,究竟是什么阻止了编译器接受以下语法:

const int multi_arr1[][] = {{1,2,3}, {1,2,3}}; // why not?
const int multi_arr2[][3] = {{1,2,3}, {1,2,3}}; // OK

error: declaration of 'multi_arr1' as multidimensional array must have bounds
       for all dimensions except the first

是什么阻止了编译器向右看并意识到我们正在为每个“子数组”处理 3 个元素,或者仅在程序员通过的情况下才可能返回错误,例如每个子数组的元素数量不同,例如 {1,2,3}, {1,2,3,4}?

例如,在处理一维字符数组时,编译器可以查看= 右侧的字符串,这是有效的:

const char str[] = "Str";

我想了解正在发生的事情,以便编译器无法推断数组维度并计算分配大小,因为现在在我看来,编译器拥有执行此操作所需的所有信息。我在这里错过了什么?

【问题讨论】:

  • 什么“阻止”编译器遵守标准(对于 C C++,它们是不同的标准,选择一个)。阻止标准允许这样做的原因是没有人为实施它写过标准提案,随后被接受
  • ^ - 那。这告诉您很多关于此功能的真正需求在实践中可能出现的程度
  • 关于不同大小的初始化器应该是错误还是尺寸应该是最大的初始化器的争论将持续数十年。
  • "是什么阻止了编译器查看..." --> Little 阻止了它。 “为什么...不可能”--> C 缺乏特性:二进制常量、函数重载。需要处理新生的 Unicode 支持,_Generic。 [][] = {{…}, {…}} 不是更改规范的优先事项 - 即使它很有趣。

标签: c++ c multidimensional-array compile-time


【解决方案1】:

要求编译器从初始化器推断内部尺寸将要求编译器以标准避免的方式追溯工作。

该标准允许正在初始化的对象引用它们自己。例如:

struct foo { struct foo *next; int value; } head = { &head, 0 };

这定义了一个最初指向自身的链表节点。 (据推测,稍后会插入更多节点。)这是有效的,因为 C 2011 [N1570] 6.2.1 7 表示标识符 head“具有在其声明符完成后开始的范围。” declarator 是声明的语法部分,包括标识符名称以及声明的数组、函数和/或指针部分(例如,f(int, float)*a[3] 是声明符,在 float f(int, float)int *a[3] 等声明中)。

由于 6.2.1 7,程序员可以编写这个定义:

void *p[][1] = { { p[1] }, { p[0] } };

考虑初始化器p[1]。这是一个数组,因此它会自动转换为指向其第一个元素p[1][0] 的指针。编译器知道该地址,因为它知道p[i] 是一个包含1 个void * 的数组(对于i 的任何值)。如果编译器不知道p[i] 有多大,它就无法计算这个地址。所以,如果 C 标准允许我们这样写:

void *p[][] = { { p[1] }, { p[0] } };

那么编译器将不得不继续扫描p[1],以便它可以计算为第二维提供的初始化程序的数量(在这种情况下只有一个,但我们必须至少扫描到}才能看到,可能还有很多),然后返回计算p[1]的值。

该标准避免强制编译器执行这种多遍工作。要求编译器推断内部尺寸会违反此目标,因此标准不会这样做。

(事实上,我认为标准可能不需要编译器执行有限数量的前瞻操作,可能在标记化期间只需要几个字符,在解析语法时可能只需要一个标记,但我不确定. 有些东西有直到链接时才知道的值,例如void (*p)(void) = &SomeFunction;,但那些是由链接器填写的。)

此外,考虑如下定义:

char x[][] =
    {
        {  0,  1 },
        { 10, 11 },
        { 20, 21, 22 }
    };

当编译器读取前两行初始值时,它可能希望在内存中准备一份数组副本。因此,当它读取第一行时,它将存储两个值。然后它看到了线的末端,所以它可以暂时假设内部维度是 2,形成char x[][2]。当它看到第二行时,它会分配更多内存(与 realloc 一样)并继续,将接下来的两个值 10 和 11 存储在适当的位置。

当它读取第三行并看到22时,它意识到内部维度至少是三个。现在编译器不能简单地分配更多的内存。它必须重新排列 10 和 11 在内存中相对于 0 和 1 的位置,因为它们之间有一个新元素; x[0][2] 现在存在并且值为 0(到目前为止)。因此,要求编译器推断内部维度,同时还允许每个子数组中有不同数量的初始化器(并根据整个列表中看到的初始化器的最大数量推断内部维度)会给编译器带来大量内存运动。

【讨论】:

  • 顺便说一下,C99 确实允许类似:int *q[5] = {(int[]){1,2,3,-1}, (int[]){1,2,-1}, (int[]){1,2,3,4,5,6,7,-1}};。语法有点笨拙,代码需要使用(int*)[] 而不是二维数组,并且没有很好的方法来找出内部维度,除非它被数据暗示[例如通过在每行的末尾包含哨兵],但如果行具有不同数量的初始化器,则该方法可能比尝试使用二维数组更有效。
【解决方案2】:

在实现编译器时没有什么不可能在存在初始化器的情况下推断多维数组的最内层维度,但是,这是 C 或 C++ 标准不支持的功能,并且,显然,对这个功能的需求并不大。

换句话说,标准语言不支持您所追求的。如果有足够多的人需要,它可以得到支持。他们没有。

【讨论】:

    【解决方案3】:

    简要扩展评论:

    什么“阻碍”编译器遵守标准(对于 C C++,它们是不同的标准,选择一个)。

    “阻止”标准允许这样做的原因是没有人为实施它编写了标准提案,该提案随后被接受。

    所以,你要问的只是为什么没有人有动力去做你认为有用的事情,而我只能认为这是基于意见的。

    可能在实现这一点或保持一致的语义方面存在实际困难;这不是您问的确切问题,但至少可以客观地回答。我怀疑如果有足够的动力,有人可以克服这些困难。大概没人。

    例如,(reference),语法a[] 真正的意思是未知边界的数组。因为在使用聚合初始化声明边界时可以在特殊情况下推断边界,所以您将其视为a[auto] 之类的东西。也许那个会是一个更好的提议,因为它没有历史包袱。如果您认为这样做的好处是值得的,请随时自行编写。

    【讨论】:

      【解决方案4】:

      规则是编译器仅通过给定的初始化列表确定数组的第一维。它期望明确指定第二维。期间。

      【讨论】:

        【解决方案5】:

        对于数组,编译器必须知道每个元素有多大才能进行索引计算。例如

        int a[3];
        

        是一个整数数组。编译器知道int 有多大(通常为4 个字节),因此它可以计算a[x] 的地址,其中x 是0 到2 之间的索引。

        二维数组可以被认为是数组的一维数组。例如

        int b[2][3];
        

        int 的二维数组,但它也是int 数组的一维数组。即b[x] 指的是三个ints 的数组。

        即使是数组数组,编译器必须知道每个元素的大小的规则仍然适用,这意味着在数组数组中,第二个数组必须是固定大小的。如果不是,编译器在索引时无法计算地址,即b[x] 将无法计算。因此,在您的示例中multi_arr2 的原因是可以的,但multi_arr1 不是。

        是什么阻止了编译器向右看并声称我们正在为每个“子数组”处理 3 个元素,或者仅在程序员通过的情况下才可能返回错误,例如每个子数组的元素数量不同,例如 {1,2,3}, {1,2,3,4}

        可能是解析器的限制。当它到达初始化器时,解析器已经通过了声明。最早的 C 编译器非常有限,并且在现代编译器出现之前很久就按照预期设置了上述行为。

        【讨论】:

        • 还有将数组传递给函数的问题。除非编译器传递附加(隐藏)信息,否则函数无法知道维度。传递这些信息会打破“数组只是一个指针”的范式。
        • @jamesqf 我打算写一些关于传递参数的东西,但我忘了。在函数声明中,您需要指定二维数组中最后一个索引的大小。
        猜你喜欢
        • 2021-03-28
        • 2014-02-17
        • 2011-10-11
        • 1970-01-01
        • 1970-01-01
        • 2017-09-17
        • 2019-05-08
        • 2014-12-28
        相关资源
        最近更新 更多