clang 对全局变量的行为不同

问题描述

我有这些由 3 个文件组成的虚拟软件:

test.h

int gv;
void set(int v);

test.c

#include "test.h"

void set(int x) {
    gv = x;
}

main.c

#include "test.h"
#include <assert.h>

int main() {
    set(1);
    assert(gv == 1);
}

代码在 MSVC 2019 和 GCC 8 中编译并运行良好,但使用 clang(Visual Studio 2019 提供的 clang-cl 11)在链接时失败,抱怨 gv 已定义:

1>------ Build started: Project: test,Configuration: Debug x64 ------
1>lld-link : error : undefined symbol: gv
1>>>> referenced by ...\test\main.c:6
1>>>>               x64\Debug\main.obj:(main)
1>>>> referenced by ...\test\test.c:4
1>>>>               x64\Debug\test.obj:(set)
1>Done building project "test.vcxproj" -- FAILED.

我知道 extern 是在文件范围内定义的对象的默认 storage-class specifier,但是如果我明确指定 externint gv,它会中断与每个编译器的链接(当然,除非我在源文件中为 gv 添加定义)。

有一点我不明白。发生了什么?

解决方法

int gv;gv暂定定义,根据 C 2018 6.9.2 2. 当翻译单元中没有常规定义时(正在编译的文件)包括它所包含的一切),一个临时定义变成了一个初始化器为零的定义。

由于此暂定定义包含在 test.cmain.c 中,因此 test.cmain.c 中都有暂定定义。当这些链接在一起时,您的程序就有两个定义。

C 标准没有定义当具有外部链接的相同标识符有两个定义时的行为。 (有两个定义违反了 C 2018 6.9 5 中的“shall”要求,并且标准没有定义违反要求时的行为。)由于历史原因,一些编译器和链接器将暂定定义视为“公共符号”定义将被链接器合并——具有相同符号的多个暂定定义将被解析为单个定义。而有些则没有;有些人将暂定定义视为常规定义,如果有多个定义,链接器会抱怨。这就是您看到不同编译器之间存在差异的原因。

要解决此问题,您可以将 int gv; 中的 test.h 更改为 extern int gv;,这使其成为不是定义(甚至不是临时定义)的声明。然后您应该将 int gv;int gv = 0; 放在 test.c 中以提供程序的一个定义。另一种解决方案是使用 -fcommon 开关,如下所示。

默认行为在 GCC 版本 10 中发生了变化(在某些时候可能还有 Clang;我的 Apple Clang 11 的行为与您的报告不同)。使用 GCC 和 Clang,您可以使用命令行开关 -fcommon(将暂定定义视为公共符号)或 -fno-common(如果有多个暂定定义导致链接器错误)选择所需的行为.

一些附加信息是 herehere

,

我知道 extern 是在文件范围内定义的对象的默认存储类说明符

确实如此,但链接因 gv 符号的“重新定义”而中断,不是吗?

这是因为 test.cmain.c 在预处理器包含标头后都有 int gv;。因此最终对象 test.omain.o 都包含 _gv 符号。

最常见的解决方案是在 extern int gv; 头文件中包含 test.h(它告诉编译器 gv 存储分配在其他地方)。在 C 文件中,例如 main.c,定义 int gv; 以便 gv 的存储将实际分配,但仅在 main.o 对象内部分配一次。


编辑:

引用您提供的相同链接 storage-class specifier,其中包含以下声明:

带有外部链接的声明通常在头文件中可用,以便所有 #include 文件的翻译单元可以引用在别处定义的相同标识符。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...