我在用 clang 编译时发现了一个好奇心(在 MacBook 上,如果有帮助的话)。假设我有两个文件:
废话.c
int *p;
Run Code Online (Sandbox Code Playgroud)
主程序
#include <stdio.h>
extern int *p;
int main() {
printf("%p\n", p);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
如果我编译
clang blah.c main.c
Run Code Online (Sandbox Code Playgroud)
一切顺利。但是,如果我这样做
clang -c blah.c
ar rcs libblah.a blah.o
clang main.c libblah.a
Run Code Online (Sandbox Code Playgroud)
我收到链接器错误:
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)
Run Code Online (Sandbox Code Playgroud)
有趣的是,如果我在 blah.c 中初始化变量,
clang blah.c main.c
Run Code Online (Sandbox Code Playgroud)
错误消失。
此外,使用 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
Run Code Online (Sandbox Code Playgroud)
这里的 clang 究竟是怎么回事?
TL;DR:您的 Clang 有一个错误。您可以通过添加-fno-common编译选项来解决这个问题,而无需修改代码。
您的代码的两种变体都是正确的,并且就 C 语言规范而言,它们具有相同的含义。在我的 Linux 机器上,GCC 8.5 和 Clang 12 都接受这两种变体并成功构建工作可执行文件,无论blah.o是直接链接还是从库链接。
但是,如果您用来nm检查使用和不使用初始化程序构建的库p,您可能会得到有关正在发生的情况的提示。如果没有初始化程序,我会看到(使用任一编译器)p具有类型“C”(通用)。使用初始值设定项(为 null),我看到它的类型为“B”(BSS)。
这反映了 Unix C 实现的传统行为:合并同一符号的多个定义,只要使用显式初始化器定义的不超过一个。这是对标准 C 的扩展,因为该语言要求程序引用的每个外部符号都只有一个定义。除此之外,该扩展还涵盖了extern在标头中省略变量声明的常见错误,前提是标头未指定初始值设定项。
为了实现这一点,工具链需要区分使用显式初始化程序定义的符号和没有显式初始化程序定义的符号,这就是(对于 C)符号类型“common”的用武之地——它用于传达已定义但没有显式初始化程序的符号。显式初始化程序。典型的链接器行为是将所有此类符号视为未定义的符号,如果正在链接的对象之一具有不同类型的该符号的定义,或者将除其中一个之外的所有符号视为未定义,而另一个则视为具有类型 B (意味着默认初始化)。
但MacOS开发工具链似乎已经孵化出一个bug。在您的示例中,当类型 C 符号出现在库中时,它错误地无法将类型 C 符号识别为可行的定义。问题可能出在 Clang 前端或系统链接器中,或者两者的组合中。也许这与苹果最近收紧(以及随后重新放松)编译器默认一致性设置有关。
--fno-common您可以通过添加C 编译器标志来解决此问题。GCC 和 Clang 都接受禁用上述符号合并,并且至少在我的机器上,它们都通过在没有显式初始化程序的情况下定义符号时将符号作为类型 B 发出来实现这一点,就像它已被显式初始化一样到一个空指针。但请注意,这将破坏当前依赖于该合并行为的任何代码。