linux 为什么ld不能忽略未使用的未解析符号?

wvt8vs2t  于 2022-11-02  发布在  Linux
关注(0)|答案(2)|浏览(148)

请考虑以下源文件:

a.c

extern int baz();

int foo() { return 123; }
int bar() { return baz() + 1; }

B.c:

extern int foo();

int main() { return foo(); }

现在,当我尝试使用这些源代码构建程序时,会发生以下情况:

$ gcc -c -o a.o a.c
$ gcc -c -o b.o b.c
$ gcc -o prog a.o b.o
/usr/bin/ld: a.o: in function `bar':
a.c:(.text+0x15): undefined reference to `baz'
collect2: error: ld returned 1 exit status

这是在Devuan GNU/Linux Chimaera上,带有GNU ld 2.35.2,GCC 10.2.1.
为什么会发生这种情况呢?我的意思是,不需要任何复杂的优化就可以知道foo()中并不真正需要baz()- ld在某个时候自然会注意到这一点-例如,当完成对foo()的遍历而没有注意到使用baz()的位置时。
现在,你可以说“einpoklum,你没有要求编译器为你做任何麻烦”--我想这是公平的,但是即使我在这些指令中使用-O3,我也会得到同样的错误。
注意:启用LTO和优化后,我们可以避免此问题:

$ gcc -c -flto -O1 -o b.o b.c
$ gcc -c -flto -O1 -o a.o a.c
$ gcc -o prog -O1 -flto a.o b.o
$ /prog ; echo $?;
123
ou6hu8tu

ou6hu8tu1#

如果你使用gcc和binutils ld来编译你的程序,你需要把函数放在不同的部分。它是由-fdata-sections-ffunction-sections命令行选项存档的。
如果你不想在你的可执行文件中包含死代码,你需要使用--gc-sectionsld选项来启用它。
把这些放在一起:

$ gcc -fdata-sections -ffunction-sections -c -o a.o a.c
$ gcc -c -o b.o b.c
$ gcc -Wl,--gc-sections -o prog a.o b.o
$ /prog ; echo $?
123

如果您想默认启用它,请简单构建GCC并启用这些选项。

idv4meu8

idv4meu82#

在一个“普通”的传统编译代码中:

extern int baz();

int foo() { return 123; }
int bar() { return baz() + 1; }

编译器创建一个包含两个例程的代码沿着符号foobar的定义和对baz的引用的对象模块。没有任何东西告诉链接器属于foo的代码在哪里开始和结束,其中属于X1 M4 N1 X的代码开始和结束,或者甚至任何给定的代码段-或对象模块中的任何给定字节-仅属于X1 M5 N1 X或X1 M6 N1 X之一。如果我用汇编语言编写并汇编成一个对象模块,我就可以在foo中包含跳到bar的代码(只使用汇编程序计算的硬编码偏移量,而不显示在链接器可见的任何符号中),反之亦然。
因此,连接器无法知道foobar可以分开。
后来,编译器创建了一个协议,以保持函数分离,并在对象模块中提供足够的信息,以便链接器可以确定它们在哪里分离,并告诉链接器可以分离函数。当启用了该选项时,链接器可以在程序中包含foo,而不包含bar
这个特性还不是工具中的默认特性,这是各种构建系统和项目中的遗留问题、惯性和当前实践。

相关问题