【问题标题】:Why is this statement producing a linker error with gcc?为什么这个语句会在 gcc 中产生链接器错误?
【发布时间】:2023-03-20 06:40:01
【问题描述】:

我有一段极其琐碎的 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[];

为什么在这里省略存储类会导致不同的行为,为什么第一段代码会产生链接器错误?谢谢。

【问题讨论】:

  • 废话代码 - 废话信息
  • 有趣的事实:它用 clang 编译,但不是 gcc。
  • 我对 clang 的行为感到惊讶 6.9.2p3 “如果对象标识符的声明是一个暂定定义并且具有内部链接,则声明的类型不应是不完整的类型。”
  • 6.9.2p2 的结尾给出了在翻译单元结尾未解决暂定定义时的行为——它被实例化为“该标识符的文件范围声明 [...]初始化器等于 0"; 6.9.2p5 的示例中明确涵盖了缺少数组维度的情况,其中隐式初始化程序被解释为为其提供维度 1(如 OP 的第二种情况)。也许gcc 未能真正呈现最终定义,因为它缺少暗淡?
  • 静态定义的版本使用gcc -pedantic编译失败。

标签: c gcc static


【解决方案1】:

空数组static int arr[]; 和零长度数组static int arr[0];gcc non-standard extensions

这些扩展的目的是修复旧的“struct hack”。早在 C90 年代,人们就编写了这样的代码:

typedef struct
{
  header stuff;
  ...
  int data[1]; // the "struct hack"
} protocol;

然后将使用data 的位置,就好像它具有超出数组的可变大小,具体取决于标题部分中的内容。这样的代码是错误的,将数据写入填充字节并调用数组越界未定义的行为。

gcc 通过添加空/零数组作为编译器扩展解决了这个问题,使代码的行为没有错误,尽管它不再是可移植的。

C 标准委员会认识到这个 gcc 特性很有用,因此他们在 1999 年将灵活数组成员添加到 C 语言中。从那时起,gcc 特性被视为已过时,因为使用C 标准的灵活数组成员是首选。

正如链接的 gcc 文档所承认的那样:

不鼓励在其他上下文中声明零长度数组,包括作为结构对象的内部成员或非成员对象。

这就是你的代码所做的。

请注意,没有传递编译器选项的 gcc 默认为 -std=gnu90 (gcc -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 时,您通过强制变量具有内部链接来阻止在其他地方指定数组大小的可能性。

【讨论】:

  • 这些非标准扩展可能在新的 gcc 版本中从默认的 gnu11 中删除。我不知道。
  • 感谢您提供的信息和有趣的历史记录!
  • 我知道这被标记为 gcc,但举个例子,winapi(以及微软的 C 编译器)也经常使用“struct hack”。它不仅限于 GNU 世界,不幸的是,Windows 中需要“struct hack”的 API 不会很快消失。我想这也会影响 WINE。
【解决方案2】:

这种行为的一个原因可能是编译器发出警告,导致无法访问的static 变量并对其进行优化 - 链接器会抱怨!

如果它不是静态的,则不能简单地忽略它,因为其他模块可能会引用它 - 所以链接器至少可以找到那个符号 arr

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-11-19
    • 2020-10-06
    • 1970-01-01
    • 2018-03-17
    • 2012-10-01
    • 2019-11-15
    • 2021-09-18
    • 2018-04-19
    相关资源
    最近更新 更多