clang 无法识别在静态库中找到的单元化指针

clang not recognizing unitialized pointer found in static library

我在用 clang 编译时发现了一个好奇心(在 MacBook 上,如果有帮助的话)。假设我有两个文件:

blah.c

int *p;

main.c

#include <stdio.h>

extern int *p;

int main() {
    printf("%p\n", p);
    return 0;
}

如果我用

编译
clang blah.c main.c

一切顺利。但是,如果我这样做

clang -c blah.c
ar rcs libblah.a blah.o
clang main.c libblah.a

我收到链接器错误:

Undefined symbols for architecture x86_64:
  "_p", referenced from:
      _main in test-4bf0d6.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

有趣的是,如果我在blah.c中初始化变量,

#include <stddef.h>

int *p = NULL;

错误消失。

此外,使用 gcc 编译不会产生此行为。这里的 clang 到底是怎么回事?

这是 clang --version 的输出:

Apple clang version 13.0.0 (clang-1300.0.29.30)
Target: x86_64-apple-darwin21.2.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

What exactly is going on with clang here?

TL;DR: 你的 Clang 有一个错误。您可以通过将 -fno-common 添加到编译选项来解决它而无需修改代码。


更多详情

你的代码的两种变体都是正确的,就C语言规范而言,它们具有相同的含义。在我的 Linux 机器上,GCC 8.5 和 Clang 12 都接受两种变体并成功构建工作可执行文件,无论 blah.o 是直接链接还是从库链接。

但是如果您使用 nm 检查使用和不使用 p 的初始值设定项构建的库,您可能会得到关于正在发生的事情的提示。没有初始化程序,我看到(使用任一编译器) p 具有类型 'C' (常见)。使用初始化程序(为空),我看到它的类型为 'B' (BSS).

这反映了 Unix C 实现的传统行为:合并同一符号的多个定义,只要不超过一个是使用显式初始化器定义的。这是对标准 C 的扩展,因为该语言要求程序引用的每个外部符号都只有一个定义。除其他事项外,该扩展涵盖了在 header 中从变量声明中省略 extern 的常见错误,前提是 header 未指定初始值设定项。

为了实现这一点,工具链需要区分用显式初始化器定义的符号和没有定义的符号,这就是(对于 C)符号类型“common”的用武之地——它用于传达一个符号已定义,但没有显式初始化程序。典型的链接器行为是,如果被链接的 objects 之一对该符号具有不同类型的定义,则将所有此类符号视为未定义符号,或者将除其中一个之外的所有符号视为未定义,而另一个具有类型 B(暗示默认初始化)。

但是 MacOS 开发工具链似乎已经孵化出一个错误。在您的示例中,当 C 类型符号出现在库中时,它错误地未能将其识别为可行的定义。问题可能出在 Clang 前端或系统链接器中,或两者兼而有之。也许这与 Apple 最近收紧(以及随后的 re-loosening)编译器的默认一致性设置一起出现。

您可以通过将 --fno-common 添加到您的 C 编译器标志来解决此问题。 GCC 和 Clang 都接受禁用上述符号合并,并且至少在我的机器上,它们都通过在没有显式初始化器的情况下定义符号时将符号作为类型 B 来实现,就像它已被显式初始化一样到一个空指针。但是请注意,这将破坏目前依赖于该合并行为的任何代码。