为什么 header 在一种架构中包含多次,而在另一种架构中只包含一次?

Why is a header included multiple times on one architecture, but only once in another?

我正在尝试在两种架构——amd64 和 riscv64 上编译一个程序(PARSEC 基准套件的 splash2x.raytrace 包,转换后);然而,当我尝试在两者上本地编译它时,我得到了一个我无法解释的不同行为。

具体来说,在 amd64 上它编译,在 riscv64 上它失败了很多 multiple definition of....

该程序有大约十几个 .c 个文件,一个 header,包含在所有文件中(rt.h")。

样本变量是:

# File: rt.h
INT prims_in_leafs;

导致错误的原因:

/usr/bin/ld: cr.o:/home/riscv/parsec-benchmark/ext/splash2x/apps/raytrace/obj/riscv64-linux.gcc/rt.h:750: multiple definition of `prims_in_leafs'; bbox.o:/home/riscv/parsec-benchmark/ext/splash2x/apps/raytrace/obj/riscv64-linux.gcc/rt.h:750: first defined here
/usr/bin/ld: env.o:/home/riscv/parsec-benchmark/ext/splash2x/apps/raytrace/obj/riscv64-linux.gcc/rt.h:750: multiple definition of `prims_in_leafs'; bbox.o:/home/riscv/parsec-benchmark/ext/splash2x/apps/raytrace/obj/riscv64-linux.gcc/rt.h:750: first defined here
/usr/bin/ld: fbuf.o:/home/riscv/parsec-benchmark/ext/splash2x/apps/raytrace/obj/riscv64-linux.gcc/rt.h:750: multiple definition of `prims_in_leafs'; bbox.o:/home/riscv/parsec-benchmark/ext/splash2x/apps/raytrace/obj/riscv64-linux.gcc/rt.h:750: first defined here
/usr/bin/ld: geo.o:/home/riscv/parsec-benchmark/ext/splash2x/apps/raytrace/obj/riscv64-linux.gcc/rt.h:750: multiple definition of `prims_in_leafs'; bbox.o:/home/riscv/parsec-benchmark/ext/splash2x/apps/raytrace/obj/riscv64-linux.gcc/rt.h:750: first defined here

编译使用的命令很简单:

gcc -c -static-libgcc -pthread *.c
gcc *.o -pthread -o raytrace -L/usr/lib64 -L/usr/lib -lm

现在,我发现 header 文件没有 header 保护,所以我想在某种程度上,这个文件在 amd64 上只包含一次,而在 riscv64 上多次.

另一方面,include "rt.h" 在每个文件的顶部,在任何 #ifdef 之外,所以我无法解释为什么它在 amd64 上成功,即使 [=68] =] 守卫,它在 riscv64 上仍然失败。

系统的差异是:

我想我可以手动更改所有变量等,但这需要大量工作,而且仍然无法解释差异。

发生了什么事?我怎样才能使程序编译?

示例文件在这里:

错误消息并未说明 header 被多次包含。它说相同的符号存在于多个 object 文件中,而 header 文件只是该符号的来源。

如果一个H文件定义了一个符号,那么实际上每个包含它的C文件都会定义那个符号,因为只有编译文件可以定义一个符号,而H文件是不编译的,当这些文件被包含到C文件中时C 文件被编译(实际上是在预处理时,但通常在编译时发生)。而如果多个 C 文件定义了相同的符号,被编译并链接在一起(注意这是一个 ld 错误,所以它是一个链接器错误),你最终会得到多个同名符号,即C 标准不允许。

如果 H 文件只是想通知 C 文件有关在别处定义的符号的存在和类型,例如在另一个 C 文件中,它只能通过在符号前加上 extern.

来声明符号
// Declares that a symbol of type int named "a" exists somewhere
extern int a; 

// Declares and defines a symbol of type int named "a" in the 
// current compilation context
int a; 

因此 riscv64 行为是预期的,x86_64 上的行为不是。至少在严格遵循 C 标准时。正确的做法是只在 H 文件中有 extern 声明,并在 一个且只有一个 C 文件中编译它们的匹配定义。这也是我编写 C 代码的方式,这也是我所见过的大多数 C 代码的编写方式。

但显然似乎存在对 C 标准的通用扩展。它是如此常见,以至于 C 标准本身在注释中提到了它。来自 C11 标准:

J.5 Common extensions

J.5.11 There may be more than one external definition for the identifier of an object, with or without the explicit use of the keyword extern; if the definitions disagree, or more than one is initialized, the behavior is undefined (6.9.2).

海湾合作委员会就是其中之一。当然,链接器也必须支持它,GNU 工具链也支持这个扩展,它被称为 Common Model.

在 GCC 10 之前,除非将 -fno-common 作为参数传递,否则使用 通用模型 。从 GCC 10 开始,除非您将 -fcommon 作为参数传递,否则不再使用它。

长话短说:

与体系结构无关(riscv64 vs x86_64),这是由于您曾经使用过GCC 9.3和一次GCC 10.2以及您编译的代码依赖于支持普通模型

阅读有关此主题的有趣页面(这也是我找到答案信息的地方):

https://wiki.gentoo.org/wiki/Gcc_10_porting_notes/fno_common