clang 对全局变量的行为不同
clang behaves differently with global variables
我有这些由 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)在 link 时失败抱怨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,但是如果我明确指定 extern
到 int gv
,它会破坏 link与每个编译器一起使用(当然,除非我在源文件中添加 gv
的定义)。
有些事情我不明白。发生了什么事?
I understand that extern is the default storage-class specifier for objects defined at file scope
确实如此,但是 link 年龄因 gv
符号的“重新定义”而中断,不是吗?
这是因为 test.c
和 main.c
在预处理器包含 header 之后都有 int gv;
。因此最终 objects test.o
和 main.o
都包含 _gv
符号。
最常见的解决方案是在 test.h
header 文件中包含 extern int gv;
(它告诉编译器 gv
存储分配在其他地方)。在 C 文件中,例如 main.c
,定义 int gv;
,这样 gv
的存储将实际分配一次,但只在 main.o
object 中分配一次。
编辑:
引用您提供的相同 link storage-class specifier,其中包含以下语句:
Declarations with external linkage are commonly made available in header files so that all translation units that #include the file may refer to the same identifier that are defined elsewhere.
int gv;
是 gv
的 暂定定义 ,根据 C 2018 6.9.2 2. 当翻译单元中没有常规定义时 (正在编译的文件及其包含的所有内容),暂定定义成为初始值设定项为零的定义。
由于test.c
和main.c
都包含这个暂定定义,所以test.c
和main.c
中都有暂定定义。当这些链接在一起时,您的程序有两个定义。
C 标准没有定义同一标识符有两个具有外部链接的定义时的行为。 (有两个定义违反了 C 2018 6.9 5 中的“应”要求,标准没有定义违反要求时的行为。)由于历史原因,一些编译器和链接器将暂定定义视为“公共符号”定义,将由链接器合并——具有相同符号的多个暂定定义将被解析为一个定义。有些人没有;有些将暂定定义更多地视为常规定义,如果有多个定义,链接器会抱怨。这就是为什么您会看到不同编译器之间存在差异的原因。
要解决这个问题,你可以把test.h
中的int gv;
改成extern int gv;
,这样就变成了一个非定义的声明(甚至不是暂定的定义)。那么你应该把int gv;
或int gv = 0;
放在test.c
中,为程序提供一个定义。另一种解决方案可能是使用 -fcommon
开关,如下所示。
默认行为在 GCC 版本 10 中发生了变化(在某些时候可能还有 Clang;我的 Apple Clang 11 的行为与您的报告不同)。使用 GCC 和 Clang,您可以 select 使用命令行开关 -fcommon
(将暂定定义视为通用符号)或 -fno-common
(如果存在链接器错误)所需的行为多个暂定定义)。
一些附加信息是 here and 。
我有这些由 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)在 link 时失败抱怨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,但是如果我明确指定 extern
到 int gv
,它会破坏 link与每个编译器一起使用(当然,除非我在源文件中添加 gv
的定义)。
有些事情我不明白。发生了什么事?
I understand that extern is the default storage-class specifier for objects defined at file scope
确实如此,但是 link 年龄因 gv
符号的“重新定义”而中断,不是吗?
这是因为 test.c
和 main.c
在预处理器包含 header 之后都有 int gv;
。因此最终 objects test.o
和 main.o
都包含 _gv
符号。
最常见的解决方案是在 test.h
header 文件中包含 extern int gv;
(它告诉编译器 gv
存储分配在其他地方)。在 C 文件中,例如 main.c
,定义 int gv;
,这样 gv
的存储将实际分配一次,但只在 main.o
object 中分配一次。
编辑:
引用您提供的相同 link storage-class specifier,其中包含以下语句:
Declarations with external linkage are commonly made available in header files so that all translation units that #include the file may refer to the same identifier that are defined elsewhere.
int gv;
是 gv
的 暂定定义 ,根据 C 2018 6.9.2 2. 当翻译单元中没有常规定义时 (正在编译的文件及其包含的所有内容),暂定定义成为初始值设定项为零的定义。
由于test.c
和main.c
都包含这个暂定定义,所以test.c
和main.c
中都有暂定定义。当这些链接在一起时,您的程序有两个定义。
C 标准没有定义同一标识符有两个具有外部链接的定义时的行为。 (有两个定义违反了 C 2018 6.9 5 中的“应”要求,标准没有定义违反要求时的行为。)由于历史原因,一些编译器和链接器将暂定定义视为“公共符号”定义,将由链接器合并——具有相同符号的多个暂定定义将被解析为一个定义。有些人没有;有些将暂定定义更多地视为常规定义,如果有多个定义,链接器会抱怨。这就是为什么您会看到不同编译器之间存在差异的原因。
要解决这个问题,你可以把test.h
中的int gv;
改成extern int gv;
,这样就变成了一个非定义的声明(甚至不是暂定的定义)。那么你应该把int gv;
或int gv = 0;
放在test.c
中,为程序提供一个定义。另一种解决方案可能是使用 -fcommon
开关,如下所示。
默认行为在 GCC 版本 10 中发生了变化(在某些时候可能还有 Clang;我的 Apple Clang 11 的行为与您的报告不同)。使用 GCC 和 Clang,您可以 select 使用命令行开关 -fcommon
(将暂定定义视为通用符号)或 -fno-common
(如果存在链接器错误)所需的行为多个暂定定义)。
一些附加信息是 here and