C链接器何时和为什么会排除未使用的符号?

6

我正在使用gcc进行一些测试,以理解它智能排除未使用符号的规则。

// main.c

#include <stdio.h>

void foo()
{
}

int main( int argc, char* argv[] )
{
  return 0;
}

.

// bar.c

int bar()
{
  return 42;
}

.

> gcc --version
gcc (GCC) 8.2.1 20181215 (Red Hat 8.2.1-6)
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
>
> gcc -c bar.c
> gcc -g main.c bar.o 
> nm a.out | grep "foo\|bar"
000000000040111f T bar
0000000000401106 T foo

在编译main.c时,我已经将bar.o连接到a.out中。 列出a.out的符号表后,显示未使用的函数foo()bar()都包含在可执行文件中。
> ar -r libbar.a bar.o
ar: creating libbar.a
> gcc -g main.c -L ./ -lbar
> nm a.out | grep "foo\|bar"
0000000000401106 T foo

以上,我已经将bar.o归档为libbar.a,并重新创建了a.out,这次链接时使用的是libbar.a而不是bar.o。这一次,未使用的函数foo()仍然存在,但bar()不在了。
从这个实验中,我可以得出以下“规则”:
  1. 从对象文件链接的符号始终存在于可执行文件中。(也许这就解释了为什么foo()始终存在:是否有一个临时/匿名的main.o被创建了?如果是这样,它会包含foo()
  2. 如果可执行文件与一个库链接,gcc会智能地找出不必要的符号来排除。
以上是根据这个实验得出的假设 - 但它有多正确呢?如果有人了解链接工作的复杂性,我将感激提供一些背景信息来解释正在发生的事情的原因和目的。

这个问题是关于ELF和GNU gABI的吗?不同的目标在这个领域有很大的差异。 - Florian Weimer
@FlorianWeimer - 抱歉,我对这些术语不是很熟悉。我模糊地记得ELF是可执行文件的内存布局的“格式”(?)。如果这回答了你的问题,我添加了我的"gcc --version"的输出。 - StoneThrow
1个回答

9

这基本上是正确的,但需要说明静态库的链接并不真正具有每个符号的粒度,而是具有每个成员对象文件的粒度。

例如:

如果静态库包含以下文件:

a.o 
    foo
    bar
b.o 
    baz

当出现未定义的引用 foo 时,需要解决它,a.o 将被引入,并且将一同引入符号 bar

使用编译选项 -ffunction-sections-fdata-sections 进行编译,然后使用链接选项 -Wl,--gc-sections 进行链接,可以达到每个符号的粒度效果(就像它自己的目标文件一样),但请注意编译器/链接器选项是 gcc/clang 特定的,并且会带来一些轻微的性能/代码大小成本。

-ffunction-sections 将每个函数放在它自己的节中(有点像它自己的目标文件),而 -fdata-sections 对于外部可见的全局变量也是如此。然后,-Wl,--gc-sections 导致垃圾收集器在像往常一样链接对象文件之后运行,垃圾收集器会删除所有不可达的节(=》符号)。

(如果您想要 size -A the_objectfile.o 给出函数大小,并且如果您还希望这些函数大小不会因函数的位置而略微波动(由于对齐要求),则 -ffunction-sections 也非常有用。)


1
谢谢,你回答了我的问题。你是否知道在链接对象和静态库时为什么链接器的工作方式不同?也就是说,为什么针对一个对象进行链接会导致所有符号无条件包含,但针对静态库进行链接会导致未使用符号的每个成员对象文件被排除?如果你知道这种行为背后的原理,我只是好奇想要理解一下。 - StoneThrow
@StoneThrow 我不确定。我认为这是因为对象文件没有保证具有索引,无条件地包含它们可以避免需要对每个不会被跳过的对象文件进行第二次遍历,但这只是一个猜测。 - Petr Skocik
1
--gc-sections 并不是链接的“符号粒度”。链接库仍然是按照每个目标文件的粒度进行链接(在存在弱引用或具有强替换的弱定义时,这可能会产生很大的差异),但是在完全解析被链接的对象集之后,将对每个函数/数据对象部分引用进行垃圾回收,以删除不可达的引用。 - R.. GitHub STOP HELPING ICE
@R.. 感谢澄清。我回答的那部分有点粗糙。我已经编辑了您的信息。 - Petr Skocik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接