我有一段非常琐碎的C代码:
static int arr[];
int main(void) {
*arr = 4;
return 0;
}
我知道第一条语句是非法的(我已经声明了具有静态存储持续时间和文件链接但没有指定大小的文件范围数组),但是为什么会导致链接器错误? :
/usr/bin/ld: /tmp/cch9lPwA.o: in function `main':
unit.c:(.text+0xd): undefined reference to `arr'
collect2: error: ld returned 1 exit status
编译器难道不能在链接器之前捕获到这一点?
让我感到奇怪的是,如果我省略了static
存储类,则编译器会简单地假设数组的长度为1
,并且不会产生任何错误:
int arr[];
int main(void) {
*arr = 4;
return 0;
}
结果:
unit.c:5:5: warning: array 'arr' assumed to have one element
int arr[];
为什么在这里省略存储类会导致不同的行为,为什么第一段代码会产生链接器错误?谢谢。
答案 0 :(得分:11)
空数组static int arr[];
和零长度数组static int arr[0];
为gcc non-standard extensions。
这些扩展的目的是为了解决旧的“结构黑客”。早在C90年代,人们就编写了如下代码:
typedef struct
{
header stuff;
...
int data[1]; // the "struct hack"
} protocol;
其中data
会被使用,就好像它具有超出数组的可变大小一样,这取决于标题部分中的内容。这样的代码是错误的,通常将数据写入填充字节,并通常调用数组超出范围的未定义行为。
gcc通过添加空/零数组作为编译器扩展来解决此问题,尽管该代码不再具有可移植性,但该代码的行为没有错误。
C标准委员会认识到该gcc功能很有用,因此他们在1999年向C语言添加了 flexible array成员。此后,由于使用了gcc功能,因此已被视为过时C标准的灵活数组成员是首选。
由链接的gcc文档识别:
不建议在其他情况下声明零长度数组,包括作为结构对象的内部成员或作为非成员对象。
这就是您的代码所要做的。
请注意,未传递任何编译器选项的gcc默认为-std=gnu90
(gcc <5.0)或-std=gnu11
(gcc> 5.0)。这样会为您启用所有非标准扩展,因此程序可以编译但不链接。
如果您要符合标准的行为,则必须编译为
gcc -std=c11 -pedantic-errors
-pedantic
标志禁用gcc扩展,并且链接器错误将按预期切换到编译器错误。对于像您这样的空数组,您将得到:
错误:'arr'中缺少数组大小
对于零长度数组,您将得到:
错误:ISO C禁止大小为零的数组“ arr” [-Wpedantic]
int arr[]
起作用的原因是,这是具有外部链接的tentative definition的数组声明(请参见C17 6.9.2)。它是有效的C,可以视为前向声明。这意味着在代码的其他地方,编译器(或更确切地说,链接器)应该期望找到例如int arr[10]
,然后它引用同一变量。这样,可以在知道大小之前在代码中使用arr
。 (我不建议您使用此语言功能,因为它是“意大利面条式编程”的一种形式。)
使用static
时,可以通过强制变量具有内部链接来阻止在其他地方指定数组大小的可能性。
答案 1 :(得分:0)
此行为的一个可能原因是编译器发出警告,导致未访问的static
变量并对其进行优化,链接器会抱怨!
如果它不是静态的,则不能简单地忽略它,因为其他模块可能会引用它-因此链接程序至少可以找到该符号arr
。