Reputation: 5245
I'm trying to compile a program (the splash2x.raytrace
package of the PARSEC benchmark suite, after transformations) on two architectures - amd64 and riscv64; however, when I try to natively compile it on both, I get a different behavior that I can't explain.
Specifically, while on amd64 it compiles, on riscv64 it fails with a lot of multiple definition of...
.
The program has around a dozen .c
files, and one header, included in all of them ("rt.h
").
A sample variable is:
# File: rt.h
INT prims_in_leafs;
which causes the errors:
/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
The commands used for the compilation are very simple:
gcc -c -static-libgcc -pthread *.c
gcc *.o -pthread -o raytrace -L/usr/lib64 -L/usr/lib -lm
Now, I've found that the header file has no header guard, so I suppose that in some way, on amd64 this file is included only once, while on riscv64 multiple times.
On the other hand, the include "rt.h"
is on top of each file, outside any #ifdef
, so I can't explain why it succeds on amd64, and even with a header guard, it still fails on riscv64.
The differences in the systems are:
I suppose I could manually change all variables etc, but that would be a lot of work, and it still doesn't explain the difference.
What's happening? How can I make the program compile?
Sample files here:
rt.h
: https://pastebin.com/QKjvSe02main.c
: https://pastebin.com/bT7meaMNcr.c
: https://pastebin.com/Trck6imWgeo.c
: https://pastebin.com/JY67u2XeUpvotes: 1
Views: 418
Reputation: 133189
The error message does not say that the header was included multiple times. It says that the same symbol exists in multiple object files and the header file is just the source of that symbol.
If an H file defines a symbol, then in fact every C file that includes it will define that symbol because only a compiled file can define a symbol and H files are not compiled, they are included into C files when those C files are compiled (actually when they are preprocessed but that usually happens when they are compiled). And if multiple C files define the same symbol, are compiled, and linked together (note that this is a ld
error, so it is a linker error), you end up with multiple symbols of the same name, which is not allowed by C standard.
If a H file just wants to inform a C file about the existence and type of a symbol that is defined elsewhere, e.g. in another C file, it must only declare the symbol by prefixing it with 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;
So the behavior riscv64 is expected, the behavior on x86_64 isn't. At least when strictly following the C standard. Correct would be to just have extern
declarations in the H file and matching definitions for them in one and only one C file compiled. This is also how I write C code and this is also how most C code I've seen has been written.
But apparently it seems that there exits a common extension to the C standard. It is so common, that the C standard itself mentions it in an annotation. From the C11 standard:
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).
GCC is one of them. Of course, the linker has to support that as well and the GNU tool chain does support this extension, it's called the Common Model.
Prior to GCC 10 the Common Model was used unless -fno-common
was passed as argument. Starting with GCC 10, it is not used anymore unless you pass -fcommon
as argument.
To make a long story short:
It is not related to the architecture (riscv64 vs x86_64), it is caused by the fact that you once use GCC 9.3 and once GCC 10.2 and the code you compile relies that the Common Model is supported.
Interesting page to read about this topic (this is also where I found the info for my answer):
https://wiki.gentoo.org/wiki/Gcc_10_porting_notes/fno_common
Upvotes: 6