使用库的C链接器优化

3
假设我有一个名为foo()的函数在某个C库中,并且我正在静态链接这个库到某个可执行文件中,但是库或可执行文件中没有调用该方法。 链接器是否通过删除函数定义来优化最终的可执行文件,还是它仍然是代码的一部分? 是否有任何链接器优化可以打开/关闭此行为?

3
你是在询问特定的工具链还是一般性的(如果是一般性的话,答案是:这取决于你的工具链)? - user694733
2
你需要指定你所指的操作系统上使用哪个连接器。 - Laurenz Albe
2
对于绝大多数系统而言:如果一个函数没有被调用(或者其地址没有被获取),那么它将不会被链接。 - Lundin
1
请不要在关于 C 的问题上打 C++ 的标签。 - Yksisarvinen
1
我猜答案是:存在可以打开/关闭此类行为的链接器。 - KamilCuk
显示剩余3条评论
2个回答

3

这取决于你的工具链,然而我的gcc-7版本没有包含它。

你可以使用objdump轻松测试它。

//foo.h
#pragma once
int foo(int x);
int foo2(int x);

//foo.c
#include "foo.h"
int foo(int x) {
    return x * 2; //whatever
}
int foo2(int x) {
    return x * 2; //whatever
}

//test.c
#include "foo.h"

int main() {
    //foo(2);
    return 0;
}

使用以下命令构建静态库:

gcc -c foo.c
ar rcs libfoo.a foo.o

您可以使用以下代码查看结果:
objdump -j .text -S libfoo.a

它应该看起来像这样:
Disassembly of section .text:

0000000000000000 <foo>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   89 7d fc                mov    %edi,-0x4(%rbp)
   7:   8b 45 fc                mov    -0x4(%rbp),%eax
   a:   01 c0                   add    %eax,%eax
   c:   5d                      pop    %rbp
   d:   c3                      retq   

000000000000000e <foo2>:
   e:   55                      push   %rbp
   f:   48 89 e5                mov    %rsp,%rbp
  12:   89 7d fc                mov    %edi,-0x4(%rbp)
  15:   8b 45 fc                mov    -0x4(%rbp),%eax
  18:   c1 e0 02                shl    $0x2,%eax
  1b:   5d                      pop    %rbp
  1c:   c3                      retq 

然而,在构建gcc test.c libfoo.a之后,它并未出现在可执行文件中。
objdump -j .text -x a.out

SYMBOL TABLE:
00000000000004f0 l    d  .text  0000000000000000              .text
0000000000000520 l     F .text  0000000000000000              deregister_tm_clones
0000000000000560 l     F .text  0000000000000000              register_tm_clones
00000000000005b0 l     F .text  0000000000000000              __do_global_dtors_aux
00000000000005f0 l     F .text  0000000000000000              frame_dummy
0000000000000680 g     F .text  0000000000000002              __libc_csu_fini
0000000000000610 g     F .text  0000000000000065              __libc_csu_init
00000000000004f0 g     F .text  000000000000002b              _start
00000000000005fa g     F .text  000000000000000b              main

当您在test.c中使用该函数时,它将出现在符号表中。 但是,如果库包含多个函数,则无论其使用情况如何,所有函数都将被包括在内。
SYMBOL TABLE:
00000000000004f0 l    d  .text  0000000000000000              .text
0000000000000520 l     F .text  0000000000000000              deregister_tm_clones
0000000000000560 l     F .text  0000000000000000              register_tm_clones
00000000000005b0 l     F .text  0000000000000000              __do_global_dtors_aux
00000000000005f0 l     F .text  0000000000000000              frame_dummy
00000000000006a0 g     F .text  0000000000000002              __libc_csu_fini
000000000000061d g     F .text  000000000000000f              foo2
0000000000000630 g     F .text  0000000000000065              __libc_csu_init
000000000000060f g     F .text  000000000000000e              foo
00000000000004f0 g     F .text  000000000000002b              _start
00000000000005fa g     F .text  0000000000000015              main

你可以使用编译器标志解决这个问题(在gcc上)。 (请参见https://gcc.gnu.org/onlinedocs/gnat_ugn/Compilation-options.html
思路是,使用-ffunction-sections -fdata-sections构建所有对象文件,这将为每个函数创建一个唯一的部分。(通常它们都在同一个.text文件中)
gcc -c foo.c -ffunction-sections -fdata-sections
ar rcs libfoo.a foo.o
objdump -S libfoo.a

将显示这个:

Disassembly of section .text.foo:

0000000000000000 <foo>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   89 7d fc                mov    %edi,-0x4(%rbp)
   7:   8b 45 fc                mov    -0x4(%rbp),%eax
   a:   01 c0                   add    %eax,%eax
   c:   5d                      pop    %rbp
   d:   c3                      retq   

Disassembly of section .text.foo2:

0000000000000000 <foo2>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   89 7d fc                mov    %edi,-0x4(%rbp)
   7:   8b 45 fc                mov    -0x4(%rbp),%eax
   a:   c1 e0 02                shl    $0x2,%eax
   d:   5d                      pop    %rbp
   e:   c3                      retq 

使用-Wl,--gc-sections将启用链接过程中的垃圾收集,未使用的部分将被忽略/删除。

gcc test.c libfoo.a -Wl,--gc-sections
objdump -j .text -x a.out

将会导致这个结果:
SYMBOL TABLE:
00000000000004f0 l    d  .text  0000000000000000              .text
0000000000000520 l     F .text  0000000000000000              deregister_tm_clones
0000000000000560 l     F .text  0000000000000000              register_tm_clones
00000000000005b0 l     F .text  0000000000000000              __do_global_dtors_aux
00000000000005f0 l     F .text  0000000000000000              frame_dummy
0000000000000690 g     F .text  0000000000000002              __libc_csu_fini
0000000000000620 g     F .text  0000000000000065              __libc_csu_init
000000000000060f g     F .text  000000000000000e              foo
00000000000004f0 g     F .text  000000000000002b              _start
00000000000005fa g     F .text  0000000000000015              main

0
通常情况下,链接器应该能够进行这种优化,因为这往往是进行静态链接的原因(而不是通常更可取的动态链接)。

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