为什么 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 上仍然失败。
系统的差异是:
- amd64:ubuntu 上的 GCC 9.3,riscv64=10.2
- riscv64:Fedora 上的 GCC 10.2
我想我可以手动更改所有变量等,但这需要大量工作,而且仍然无法解释差异。
发生了什么事?我怎样才能使程序编译?
示例文件在这里:
rt.h
: https://pastebin.com/QKjvSe02
main.c
: https://pastebin.com/bT7meaMN
cr.c
: https://pastebin.com/Trck6imW
geo.c
: https://pastebin.com/JY67u2Xe
错误消息并未说明 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
我正在尝试在两种架构——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 上仍然失败。
系统的差异是:
- amd64:ubuntu 上的 GCC 9.3,riscv64=10.2
- riscv64:Fedora 上的 GCC 10.2
我想我可以手动更改所有变量等,但这需要大量工作,而且仍然无法解释差异。
发生了什么事?我怎样才能使程序编译?
示例文件在这里:
rt.h
: https://pastebin.com/QKjvSe02main.c
: https://pastebin.com/bT7meaMNcr.c
: https://pastebin.com/Trck6imWgeo.c
: https://pastebin.com/JY67u2Xe
错误消息并未说明 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