GCC版本升级引发多定义链接错误:为何GCC7.5.0可正常编译?
我正在将交叉编译工具链从gcc-7.5.0升级至gcc 11.4.0。使用gcc 7.5.0时,下述源码文件可编译成功,但切换至gcc 11.4.0后出现链接错误:
PATH-TO-CROSSTOOLS/ld:
test_beta/libtest_beta.a(test_b.o):/common.h:1: multiple
definition of `g_common';
test_beta/libtest_beta.a(test_a.o):/common.h:1: first
defined here
PATH-TO-CROSSTOOLS/ld:
test_beta/libtest_beta.a(test_c.o):/common.h:1: multiple
definition of `g_common';
test_beta/libtest_beta.a(test_a.o):/common.h:1: first
defined here
我理解该错误源于在C头文件中定义变量,但代码来自第三方库,不便修改。我的问题是:为何gcc 7.5.0能成功编译这些代码?我认为无论使用哪个版本都应编译失败。
相关代码
common.h
int g_common; int comp(int a, int b); int plus(int a, int b); int minus(int a, int b);
test_a.c
#include <stdio.h> #include "common.h" int comp(int a, int b){ g_common = 1; printf("g_common: %d\n", g_common); return (a > b); }
test_b.c
#include "common.h" int plus(int a, int b){ return (a + b); }
test_c.c
#include "common.h" int minus(int a, int b){ return (a - b); }
编译脚本
gcc 7.5.0编译脚本
#!/bin/sh echo "Clean build dir" rm -rf test_alpha/* for src in test_a test_b test_c; do echo "Build $src.c" <PATH-TO-CROSSTOOLS>/aarch64-openwrt-linux-gcc -fno-builtin -Wno-ignored-qualifiers -g -O2 -Wall -Wbad-function-cast -Wmissing-declarations -Wmissing-format-attribute -Wmissing-prototypes -Wmultichar -Wstrict-prototypes -Wdiv-by-zero -Wpointer-arith -Wextra -Wchar-subscripts -Wdeprecated-declarations -Winvalid-pch -Wnested-externs -Wsystem-headers -Wunused -fPIC -rdynamic -Werror -Wformat-nonliteral -Wmissing-noreturn -Wwrite-strings -Wshadow -Wswitch-enum -Wswitch-default -c -I. $src.c -o test_alpha/$src.o done echo "making libtest_alpha.a" <PATH-TO-CROSSTOOLS>/aarch64-openwrt-linux-ar rc test_alpha/libtest_alpha.a test_alpha/test_a.o test_alpha/test_b.o test_alpha/test_c.o echo "making libtest_alpha.so" <PATH-TO-CROSSTOOLS>/aarch64-openwrt-linux-gcc -fno-builtin -Wno-ignored-qualifiers -g -O2 -Wall -Wbad-function-cast -Wmissing-declarations -Wmissing-format-attribute -Wmissing-prototypes -Wmultichar -Wstrict-prototypes -Wdiv-by-zero -Wpointer-arith -Wextra -Wchar-subscripts -Wdeprecated-declarations -Winvalid-pch -Wnested-externs -Wsystem-headers -Wunused -fPIC -rdynamic -Werror -Wformat-nonliteral -Wmissing-noreturn -Wwrite-strings -Wshadow -Wswitch-enum -Wswitch-default -shared -Wl,-soname,libtest_alpha.so -Wl,--whole-archive test_alpha/libtest_alpha.a -Wl,--no-whole-archive -o test_alpha/libtest_alpha.so
gcc 11.4.0编译脚本
#!/bin/sh PATH=<PATH-TO-CROSSTOOLS>:$PATH echo "Clean build dir" rm -rf test_beta/* for src in test_a test_b test_c; do echo "Build $src.c"; aarch64-openwrt-linux-gcc --sysroot=<PATH-TO-SYSROOT>/recipe-sysroot -fno-builtin -Wno-ignored-qualifiers -g -O2 -Wall -Wbad-function-cast -Wmissing-declarations -Wmissing-format-attribute -Wmissing-prototypes -Wmultichar -Wstrict-prototypes -Wdiv-by-zero -Wpointer-arith -Wextra -Wchar-subscripts -Wdeprecated-declarations -Winvalid-pch -Wnested-externs -Wsystem-headers -Wunused -fPIC -rdynamic -Werror -Wformat-nonliteral -Wmissing-noreturn -Wwrite-strings -Wshadow -Wswitch-enum -Wswitch-default -c -I. $src.c -o test_beta/$src.o done echo "making libtest_beta.a" aarch64-openwrt-linux-ar rc test_beta/libtest_beta.a test_beta/test_a.o test_beta/test_b.o test_beta/test_c.o echo "making libtest_beta.so" aarch64-openwrt-linux-gcc --sysroot=<PATH-TO-SYSROOT>/recipe-sysroot -fno-builtin -Wno-ignored-qualifiers -g -O2 -Wall -Wbad-function-cast -Wmissing-declarations -Wmissing-format-attribute -Wmissing-prototypes -Wmultichar -Wstrict-prototypes -Wdiv-by-zero -Wpointer-arith -Wextra -Wchar-subscripts -Wdeprecated-declarations -Winvalid-pch -Wnested-externs -Wsystem-headers -Wunused -fPIC -rdynamic -Werror -Wformat-nonliteral -Wmissing-noreturn -Wwrite-strings -Wshadow -Wswitch-enum -Wswitch-default -shared -Wl,-soname,libtest_beta.so -Wl,--whole-archive test_beta/libtest_beta.a -Wl,--no-whole-archive -o test_beta/libtest_beta.so
为何gcc 7.5.0能编译通过?
这是因为gcc的默认行为在不同版本中发生了变化:
- 在gcc 7及更早版本中,对于头文件中未加
static或extern的全局变量定义,编译器默认会将其视为弱符号(weak symbol)。当链接器遇到多个相同的弱符号时,会自动选择其中一个,不会报错。 - 从gcc 10开始,编译器默认启用了
-fno-common选项(之前该选项默认关闭)。这个选项会让编译器将未加修饰的全局变量定义视为强符号(strong symbol),链接器遇到多个强符号定义时就会抛出“multiple definition”错误——这才是符合C标准的严格行为。
简单来说,旧版gcc对这种不符合标准的写法做了妥协,新版gcc则严格遵循C标准,所以升级后会报错。
不修改第三方代码的解决方法
既然无法修改第三方库的代码,可以通过添加编译选项兼容旧行为:
在gcc 11.4.0的编译脚本中,给所有gcc编译命令加上-fcommon选项,让编译器回到旧版处理方式,将全局变量视为弱符号,链接器就不会报错了。
修改后的编译命令示例:
aarch64-openwrt-linux-gcc --sysroot=<PATH-TO-SYSROOT>/recipe-sysroot -fcommon -fno-builtin -Wno-ignored-qualifiers -g -O2 -Wall -Wbad-function-cast -Wmissing-declarations -Wmissing-format-attribute -Wmissing-prototypes -Wmultichar -Wstrict-prototypes -Wdiv-by-zero -Wpointer-arith -Wextra -Wchar-subscripts -Wdeprecated-declarations -Winvalid-pch -Wnested-externs -Wsystem-headers -Wunused -fPIC -rdynamic -Werror -Wformat-nonliteral -Wmissing-noreturn -Wwrite-strings -Wshadow -Wswitch-enum -Wswitch-default -c -I. $src.c -o test_beta/$src.o
注意要在编译.o文件和生成.so文件的gcc命令中都加上-fcommon选项。
内容的提问来源于stack exchange,提问作者Ryan

