如何用GCC和ld删除未使用的C/C++符号?

Yip*_*Yay 107 c c++ gcc strip ld

我需要严格优化可执行文件的大小(ARM开发),我注意到在我当前的构建方案(gcc+ ld)中,未使用的符号不会被剥离.

的用法arm-strip --strip-unneeded为生成的可执行文件/库不改变可执行文件的输出大小(我不知道为什么,也许它根本不能).

修改我的构建管道的方式是什么(如果存在),以便从结果文件中删除未使用的符号?


我甚至都不会想到这一点,但我当前的嵌入式环境并不是非常"强大",甚至500K可以节省2M非常好的加载性能.

更新:

不幸的是,gcc我使用的当前版本没有-dead-strip选项,并且-ffunction-sections... + --gc-sectionsfor ld不会对结果输出产生任何显着差异.

我很震惊,这甚至成了问题,因为我确信gcc + ld应该自动删除未使用的符号(为什么他们甚至要保留它们?).

J T*_*J T 129

对于海湾合作委员会来说,这分两个阶段完成:

首先编译数据,但告诉编译器将代码分成翻译单元中的单独部分.这将通过使用以下两个编译器标志来完成函数,类和外部变量:

-fdata-sections -ffunction-sections
Run Code Online (Sandbox Code Playgroud)

使用链接器优化标志将转换单元链接在一起(这会导致链接器丢弃未引用的部分):

-Wl,--gc-sections
Run Code Online (Sandbox Code Playgroud)

因此,如果您有一个名为test.cpp的文件,其中声明了两个函数,但其​​中一个未使用,您可以使用以下命令省略未使用的文件到gcc(g ++):

gcc -Os -fdata-sections -ffunction-sections test.cpp -o test -Wl,--gc-sections
Run Code Online (Sandbox Code Playgroud)

(注意-Os是一个额外的编译器标志,告诉GCC优化大小)

  • 请注意,根据GCC的选项说明(我测试过),这会降低可执行文件的速度. (3认同)
  • 对于“mingw”,当使用“-static”标志静态链接 libstdc++ 和 libgcc 时,这不起作用。链接器选项“-strip-all”有很大帮助,但生成的可执行文件(或 dll)仍然比 Visual Studio 生成的文件大大约 4 倍。重点是,我无法控制“libstdc++”的编译方式。应该只有一个“ld”选项。 (2认同)

Nem*_*emo 34

如果要相信这个线程,你需要提供-ffunction-sections-fdata-sections gcc,它将把每个函数和数据对象放在它自己的部分中.然后你给--gc-sectionsGNU ld删除未使用的部分.

  • @MSalters:这不是默认值,因为它违反了C和C++标准.突然间全局初始化不会发生,这导致一些非常惊讶的程序员. (5认同)
  • @MSalters:如果您可以制作一个运行静态初始化程序的补丁,当且仅当副作用对于程序的正确操作是必要的时,那就太棒了。不幸的是,我认为完美地做到这一点通常需要解决停止问题,因此有时您可能需要犯一些错误,包括一些额外的符号。这基本上就是艾拉在对这个问题的评论中所说的。(顺便说一句:“对于程序的正确运行来说不必要”是“未使用”的不同定义,与该术语在标准中的使用方式不同) (3认同)
  • @BenVoigt在C中,全局初始化不能有副作用(初始化器必须是常量表达式) (2认同)
  • @Matt:但是在C ++中不是这样的,而且它们共享相同的链接器。 (2认同)
  • @Matt:经过一些研究,我认为你是对的。仅在 C++ 中这是一个错误的假设。我认为 C 可以使用控制部分的编译指示和属性将代码放入初始化序列中,但不能通过初始化。 (2认同)

Mic*_*son 24

您需要查看您的文档以获取您的gcc和ld版本:

但对我来说(OS X gcc 4.0.1)我发现这些是ld

-dead_strip
Run Code Online (Sandbox Code Playgroud)

删除入口点或导出符号无法访问的函数和数据.

-dead_strip_dylibs
Run Code Online (Sandbox Code Playgroud)

删除入口点或导出符号无法访问的dylib.也就是说,抑制了在链接期间没有提供符号的dylib的负载命令命令的产生.当链接到运行时需要的dylib时,不应该使用此选项,因为某些间接原因(例如dylib具有重要的初始值设定项).

这个有用的选择

-why_live symbol_name
Run Code Online (Sandbox Code Playgroud)

记录对symbol_name的引用链.仅适用于-dead_strip.它可以帮助调试为什么你认为应该删除死区的东西不被删除.

在gcc/g ++ man中还有一个注释,即只有在编译时启用了优化时才会执行某些类型的死代码消除.

虽然这些选项/条件可能不适用于您的编译器,但我建议您在文档中查找类似的内容.


Shi*_*zou 20

编程习惯也有帮助; 例如,添加static到特定文件外没有访问的函数; 使用较短的符号作为符号(可以帮助一点,可能不会太多); const char x[]尽可能使用; ... 本文虽然讨论了动态共享对象,但可以包含一些建议,如果遵循这些建议,可以帮助使最终的二进制输出大小更小(如果你的目标是ELF).

  • 如何为符号选择较短的名称? (4认同)

Tim*_*mmm 16

答案是-flto.您必须将它传递给编译和链接步骤,否则它不会执行任何操作.

它实际上工作得非常好 - 将我编写的微控制器程序的大小减小到不到之前大小的50%!

不幸的是,它看起来确实有些错误 - 我的事情没有正确构建.这可能是由于我正在使用的构建系统(QBS;它是非常新的),但无论如何我建议你只在可能的情况下为最终构建启用它,并彻底测试构建.


zxc*_*cdw 13

虽然不是严格有关的符号,若去为大小-总是编译-Os-s标志.-Os优化生成的代码以获得最小的可执行文件大小,并-s从可执行文件中删除符号表和重定位信息.

有时 - 如果需要小尺寸 - 玩不同的优化标志可能 - 或可能不 - 具有重要性.例如,切换-ffast-math和/或-fomit-frame-pointer有时甚至可以节省数十个字节.


Gea*_*phy 11

在我看来,Nemo提供的答案是正确的.如果这些说明不起作用,问题可能与您正在使用的gcc/ld版本有关,作为练习我使用此处详述的说明编译了一个示例程序

#include <stdio.h>
void deadcode() { printf("This is d dead codez\n"); }
int main(void) { printf("This is main\n"); return 0 ; }
Run Code Online (Sandbox Code Playgroud)

然后我使用逐步更积极的死代码删除开关编译代码:

gcc -Os test.c -o test.elf
gcc -Os -fdata-sections -ffunction-sections test.c -o test.elf -Wl,--gc-sections
gcc -Os -fdata-sections -ffunction-sections test.c -o test.elf -Wl,--gc-sections -Wl,--strip-all
Run Code Online (Sandbox Code Playgroud)

这些编译和链接参数分别产生了大小为8457,8164和6160字节的可执行文件,这是来自'strip-all'声明的最重要的贡献.如果您无法在您的平台上产生类似的缩减,那么您的gcc版本可能不支持此功能.我在Linux Mint 2.6.38-8-generic x86_64上使用gcc(4.5.2-8ubuntu4),ld(2.21.0.20110327)


And*_*mbe 8

strip --strip-unneeded仅在可执行文件的符号表上运行.它实际上并没有删除任何可执行代码.

标准库通过将所有函数拆分为单独的目标文件来实现您所获得的结果ar.然后,如果您将结果存档链接为库(-l your_library即将选项提供给ld),则ld将仅包含实际使用的目标文件,因此也包括符号.

您也可以找到对此类似使用问题的一些回复.

  • 库中的单独目标文件仅在执行静态链接时才相关.使用共享库,可以加载整个库,但当然不包含在可执行文件中. (2认同)

Luc*_*ton 5

我不知道这是否会帮助您解决当前的困境,因为这是最近的功能,但您可以以全局方式指定符号的可见性。-fvisibility=hidden -fvisibility-inlines-hidden在编译时传递可以帮助链接器稍后删除不需要的符号。如果您正在生成一个可执行文件(而不是共享库),那就没有什么可做的了。

GCC wiki上提供更多信息(以及用于例如库的细粒度方法)。


awi*_*ebe 5

从 GCC 4.2.1 手册,部分-fwhole-program

假设当前编译单元代表正在编译的整个程序。所有公共函数和变量(除了main和 按属性合并的那些之外)都externally_visible成为静态函数,并且在影响过程中被过程间优化器更积极地优化。虽然此选项等效于static为由单个文件组成的程序正确使用关键字,但与选项结合使用--combine此标志可用于编译大多数较小规模的 C 程序,因为函数和变量对于整个组合编译单元而言是局部的,而不是用于编译单个源文件本身。