要求编译器从初始化器推断内部维度将要求编译器以标准避免的方式追溯工作。
该标准允许初始化的对象引用自身。例如:
struct foo { struct foo *next; int value; } head = { &head, 0 };
这定义了最初指向自身的链表节点。 (推测稍后会插入更多节点。)这是有效的,因为 C 2011 [N1570] 6.2.1 7 表示标识符head
“其范围在其声明符完成后开始。” A声明者是声明语法的一部分,包括标识符名称以及声明的数组、函数和/或指针部分(例如,f(int, float)
and *a[3]
是声明符,在声明中,例如float f(int, float)
or 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(到目前为止)。因此,要求编译器推断内部维度,同时还允许每个子数组中存在不同数量的初始值设定项(并根据整个列表中看到的初始值设定项的最大数量来推断内部维度)可能会给编译器带来大量内存移动的负担。