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


110

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

arm-strip --strip-unneeded所产生的可执行文件/库的用法不会更改可执行文件的输出大小(我不知道为什么,也许根本无法更改

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


我什至不会想到这一点,但是我当前的嵌入式环境不是非常“强大”,并且甚至节省500K2M结果,从而极大地提高了加载性能。

更新:

不幸的是,目前的gcc版本我使用不具备-dead-strip选项和-ffunction-sections... + --gc-sections用于ld没有给出结果输出任何显著差异。

我感到震惊的是,这甚至成为了问题,因为我确定gcc + ld应该自动剥离未使用的符号(为什么还要保留它们?)。


您怎么知道不使用符号?
zvrba 2011年

未在任何地方引用=>在最终应用程序中未使用。我认为在建立/链接时构建调用图并不是很困难。
Yippie-Ki-Yay'7

1
您是要通过删除无效符号来减小.o文件的大小,还是要在加载到可执行内存后减小实际代码占用空间的大小?您说“嵌入”的事实暗示了后者。您提出的问题似乎集中在前者上。
艾拉·巴克斯特

@Ira我试图减小输出可执行文件的大小,因为(例如)如果我尝试移植一些使用boost库的现有应用程序,则.exe由于我当前嵌入式运行时的规范,结果文件包含许多未使用的目标文件,启动10mb应用程序所花费的时间比例如启动500k应用程序要长得多。
Yippie-Ki-Yay'7

8
@Yippie:您想摆脱代码以最小化加载时间;您要删除的代码是未使用的方法/等。从图书馆。是的,您需要建立一个调用图来做到这一点。这不是那么容易。它必须是一个全局调用图,它必须是保守的(不能删除可能使用的东西)并且必须是准确的(因此,您与理想的调用图非常接近,所以您真的知道什么不是)用过的)。最大的问题是制作全局,准确的调用图。不了解执行此操作的许多编译器,更不用说链接器了。
伊拉·巴克斯特

Answers:


131

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

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

-fdata-sections -ffunction-sections

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

-Wl,--gc-sections

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

gcc -Os -fdata-sections -ffunction-sections test.cpp -o test -Wl,--gc-sections

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


3
请注意,这会按照GCC的选项说明(我已测试)降低可执行文件的速度。
变形

1
mingw当静态地将libstdc ++和libgcc与标志静态链接时,使用此功能将不起作用-static。链接器选项有-strip-all很大帮助,但生成的可执行文件(或dll)仍然比Visual Studio生成的可执行文件大四倍。关键是,我无法控制如何libstdc++编译。应该只有一个ld选择。
Fabio

34

如果相信该线程,则需要提供-ffunction-sections-fdata-sections到gcc,这会将每个函数和数据对象放在其自己的部分中。然后,您给予--gc-sectionsGNU ld并删除未使用的部分。


6
@MSalters:这不是默认值,因为它违反了C和C ++标准。突然不会发生全局初始化,这使一些程序员感到非常惊讶。
Ben Voigt

1
@MSalters:仅当您通过了建议采用默认行为的非标准行为破坏选项时。
Ben Voigt

1
@MSalters:如果并且只有当副作用对于程序的正确操作是必需的时,如果您可以制作一个运行静态初始化程序的补丁,那就太棒了。不幸的是,我认为完美地做到这一点通常需要解决停止问题,因此您可能有时需要在增加一些额外符号方面犯错。这基本上是艾拉在对问题的评论中所说的。(顺便说一句:“对于程序的正确操作不是必需的”与“未使用的”定义是与标准中使用的术语不同的定义)
Ben Voigt

2
@BenVoigt在C中,全局初始化不能有副作用(初始化器必须是常量表达式)
MM

2
@Matt:但是在C ++中不是这样的,而且它们共享相同的链接器。
Ben Voigt 2014年

25

您需要检查自己的文档中是否存在gcc和ld版本:

但是对我来说(OS X gcc 4.0.1)我找到了这些用于ld

-dead_strip

删除入口点或导出的符号无法访问的功能和数据。

-dead_strip_dylibs

删除入口点或导出的符号无法访问的dylib。也就是说,抑制了在链接期间未提供任何符号的dylib的装入命令命令的生成。当由于某些间接原因(例如dylib具有重要的初始化程序)而在运行时链接到dylib时,不应使用此选项。

这个有用的选项

-why_live symbol_name

记录对symbol_name的引用链。仅适用于-dead_strip。它可以帮助调试为什么您认为应该删除的东西为什么没有被删除。

gcc / g ++手册中还有一条注释,即只有在编译时启用优化的情况下,才能执行某些种类的死代码消除。

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


这似乎与无关mingw
法比奥

-dead_strip不是一个gcc选择。
ar2015年

20

编程习惯也可能有所帮助;例如,添加static到特定文件外部无法访问的功能;使用较短的符号名称(可能会有所帮助,可能不会太大);const char x[]尽可能使用;... 尽管本文讨论了动态共享库,但仍可以包含一些建议,如果遵循这些建议,则可以帮助减小最终二进制输出的大小(如果目标是ELF)。


4
为符号选择缩写名称有什么帮助?
fuz 2016年

1
如果没有删除符号,则可能会很可怕,但现在似乎需要说出来。
ShinTakezou '16

@fuz本文讨论的是动态共享对象(例如,.so在Linux上),因此必须保留符号名称,以便Python的ctypesFFI模块之类的API 可以在运行时使用它们按名称查找符号。
ssokolow

18

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

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

不幸的是,它似乎确实有一些错误-我有一些实例无法正确构建。可能是由于我使用的构建系统(QBS;它是很新的),但是无论如何,我建议您仅在最终构建时启用它,并彻底测试构建。


1
“ -Wl,-gc-sections”在MinGW-W64上不起作用,“-flto”对我有用。谢谢
rhbc73 '16

输出程序集很奇怪,-flto我不了解它在幕后的作用。
ar2015

我相信-flto它不会将每个文件编译为程序集,而是将它们编译为LLVM IR,然后最终链接将它们编译为好像它们都在一个编译单元中一样。这意味着它可以消除未使用的功能,并内联非功能static,可能还有其他功能。请参阅llvm.org/docs/LinkTimeOptimization.html
Timmmm,

13

虽然不严格涉及符号,但是如果要增加大小,请始终使用-Os-s标志进行编译。-Os针对最小的可执行文件大小优化结果代码,并-s从可执行文件中删除符号表和重定位信息。

有时-如果需要较小的尺寸-尝试使用不同的优化标志可能具有-或可能没有意义。例如,切换-ffast-math和/或-fomit-frame-pointer有时可以为您节省数十个字节。


只要您遵守语言标准,大多数优化调整仍将产生正确的代码,但是我对-ffast-math完全符合标准的C ++代码造成了严重破坏,因此我永远不会推荐它。
Raptor007

11

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

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

然后,我使用越来越积极的死代码删除开关来编译代码:

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

这些编译和链接参数分别生成了大小为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)


8

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

标准库通过将其所有功能拆分为单独的目标文件(使用组合在一起)来获得所需的结果ar。如果您随后将生成的归档链接为库(即,请提供选项-l your_library ld),则ld将仅包括目标文件,因此也包括实际使用的符号。

您可能还会发现一些有关此类似使用问题的答案。


2
库中单独的目标文件仅在进行静态链接时才相关。对于共享库,当然会加载整个库,但不包含在可执行文件中。
乔纳森·莱夫勒

4

我不知道这是否对您当前的困境有所帮助,因为这是一项新功能,但是您可以以全局方式指定符号的可见性。通过-fvisibility=hidden -fvisibility-inlines-hidden编译可以帮助链接器以后摆脱不需要的符号。如果您要生成可执行文件(而不是共享库),则无需执行其他操作。

在GCC Wiki上可以获得更多信息(以及例如库的细粒度方法)。


4

在GCC 4.2.1手册的以下部分中-fwhole-program

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


是的,但这大概不适用于任何类型的增量编译,并且可能会有点慢。
Timmmm 2014年

@Timmmm:我怀疑你在想-flto
Ben Voigt 2014年

是! 随后我发现了(为什么没有任何答案?)。不幸的是,这似乎有点问题,所以我只建议在最终版本中使用它,然后对它进行大量测试!
Timmmm 2014年

-1

您可以在目标文件(例如可执行文件)上使用二进制条来从中剥离所有符号。

注意:它会更改文件本身,而不创建副本。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.