繁体   English   中英

clang 对全局变量的行为不同

[英]clang behaves differently with global variables

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

测试.h

int gv;
void set(int v);

测试.c

#include "test.h"

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

主文件

#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是在文件范围内定义的对象的默认存储类说明符,但是如果我明确指定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.hextern int gv; ,这使它成为一个不是定义(甚至不是暂定定义)的声明。 那么你应该把int gv; int gv = 0; test.c中为程序提供一个定义。 另一种解决方案可能是使用-fcommon开关,如下所示。

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

一些附加信息在这里这里

我知道 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 文件的翻译单元可以引用在别处定义的相同标识符。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM