GCC 中格式错误的 C/C++ 多维数组初始化

Malformed C/C++ Multidimensional Array Initialization in GCC

我想我知道填充如何以正确的格式工作,即:

char arr[3][2] = {{1}, {4,5}};

等同于

char arr[3][2] = {1, 0, 4, 5, 0, 0};

而且,为了好玩,我决定在 GCC 上测试错误的格式,看看它 return 是什么。对于以下代码:char arr[3][3] = {{1,2,3},12,{4,5,6}};,GCC returns:

{1, 2, 3, 12, 4, 0, 0, 0, 0}

但我猜它会 return:

{1, 2, 3, 12, 4, 5, 6, 0, 0}

似乎 12 将其抛出导致 {4,5,6} 的“标量初始值设定项中的过多元素”警告,因此 56 被丢弃。

用于展平(缺少更好的术语)这些多维数组初始化的算法是什么?

GCC 是这样说的:

test.c: In function 'main':
test.c:119:2: warning: braces around scalar initializer
  char arr[3][3] = {{1,2,3},12,{4,5,6}};
  ^~~~
test.c:119:2: note: (near initialization for 'arr[1][1]')
test.c:119:36: warning: excess elements in scalar initializer
  char arr[3][3] = {{1,2,3},12,{4,5,6}};
                                  ^
test.c:119:36: note: (near initialization for 'arr[1][1]')
test.c:119:38: warning: excess elements in scalar initializer
  char arr[3][3] = {{1,2,3},12,{4,5,6}};
                                    ^
test.c:119:38: note: (near initialization for 'arr[1][1]')

C 2018 6.7.9 第 17 至 21 段讨论了如何从大括号括起来的列表初始化聚合。 17 说:

Each brace-enclosed initializer list has an associated current object. When no designations are present, subobjects of the current object are initialized in order according to the type of the current object: array elements in increasing subscript order, structure members in declaration order, and the first named member of a union. In contrast, a designation causes the following initializer to begin initialization of the subobject described by the designator. Initialization then continues forward in order, beginning with the next subobject after that described by the designator.

20 告诉我们有关包含数组的信息:

If the aggregate or union contains elements or members that are aggregates or unions, these rules apply recursively to the subaggregates or contained unions. If the initializer of a subaggregate or contained union begins with a left brace, the initializers enclosed by that brace and its matching right brace initialize the elements or members of the subaggregate or the contained union. Otherwise, only enough initializers from the list are taken to account for the elements or members of the subaggregate or the first member of the contained union; any remaining initializers are left to initialize the next element or member of the aggregate of which the current subaggregate or contained union is a part.

21 告诉我们缺少初始值设定项:

If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

现在我们可以看到 char arr[3][3] = {{1,2,3},12,{4,5,6}}; 是如何处理的。

首先,我们要初始化arr,因此,到第17段,我们将依次初始化它的三个元素arr[0]arr[1]arr[2] .由于列表 {1,2,3}12{4,5,6} 中的第一项是大括号括起来的列表,因此第 20 段告诉我们该列表中的项 {1,2,3} 将被使用初始化子聚合 arr[0].

所以arr[0][0]arr[0][1]arr[0][2]被初始化为1、2、3。

接下来,我们考虑arr[1]arr[1] 的初始化器以 12 开头,而不是左大括号,因此我们不会从大括号括起来的列表中获取初始化器。所以我们在列表中留下了两项,12{4,5,6},用于初始化 arr[1][0]arr[1][1]arr[1][2](以及稍后的arr[2]).

的元素

所以 12 初始化 arr[1][0],当然会产生 12。然后 {4,5,6} 初始化 arr[1][1].

这里有两个问题。一是第 20 段中的规则说,如果我们用大括号括起来的列表初始化 子聚合 ,则列表中的初始化器用于子聚合的元素或成员。但是我们正在尝试初始化 arr[1][1],它是一个标量对象,而不是聚合。所以规则并没有告诉我们该做什么。我相信行为没有定义。

此外,如果我们确实将这个用大括号括起来的列表作为初始值,它的值太多了,这也可能是未定义的,因为第 2 段说:

No initializer shall attempt to provide a value for an object not contained within the entity being initialized.

尽管缺少定义,GCC 似乎已经采用括号括起来的列表的第一项来初始化 arr[1][1] 并丢弃了其余部分。这似乎是合理的。相比之下,用 56 来初始化其他数组元素似乎不太合理:由于 C 标准规定的处理,整个大括号括起来的列表已经与 arr[1][1] 相关联,并且没有理由从该列表中获取值以用于其他数组元素。

假设编译器在未定义的行为之后继续,arr[1][2]arr[2] 的元素没有初始化器。因此第 21 段中关于缺少初始化器的规则适用:它们被初始化为静态对象,对于 char,这意味着它们被初始化为零。