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

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

对于生成的可执行文件/库, arm-strip --strip-unneeded使用arm-strip --strip-unneeded不会改变可执行文件的输出大小(我不知道为什么,也许根本就不行)

修改我的build筑物pipe道的方式(如果存在的话)会是什么方式,以便从结果文件中去除未使用的符号?


我甚至不会想到这一点,但是我目前的embedded式环境并不是非常“强大”,即使在2M节省了500K ,也可以获得非常好的加载性能。

更新:

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

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

对于海合会来说,这是分两个阶段完成的:

首先编译数据,但是告诉编译器将代码分离到翻译单元内的不同部分。 这将通过使用以下两个编译器标志完成函数,类和外部variables:

 -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优化大小)

如果这个线程是-ffunction-sections ,你需要提供-ffunction-sections-fdata-sections到gcc,这将把每个函数和数据对象放在它自己的部分。 然后你给--gc-sections到GNU ld去除未使用的部分。

您需要检查您的文档是否为您的gcc&ld版本:

但是对于我(OS X gcc 4.0.1)我find这些为ld

 -dead_strip 

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

 -dead_strip_dylibs 

删除入口点或导出符号无法访问的dylib。 即,禁止在链接期间不提供符号的dylibs生成加载命令命令。 链接到运行时需要的dylib时,不应该使用这个选项,因为dylib有一个重要的初始值设定项。

而这个有用的选项

 -why_live symbol_name 

logging对symbol_name的引用链。 只适用于-dead_strip 。 它可以帮助debugging为什么你认为应该死掉去除的东西不会被删除。

在gcc / g ++ man中还有一个注释,那就是只有在编译时启用了优化才能执行某些types的死代码清除。

虽然这些选项/条件可能不适合您的编译器,但我build议您在文档中寻找类似的东西。

编程习惯也可以帮助; 例如将static添加到不在特定文件外部访问的函数; 使用较短的名称符号(可以帮助一点,可能不会太多); 尽可能使用const char x[] ; … 本文尽pipe讨论了dynamic共享对象,但可以包含一些build议,如果遵循这些build议,可以帮助使最终的二进制输出大小更小(如果您的目标是ELF)。

虽然不严格符号,如果要大小 – 总是用-Os-s标志编译。 -Os优化最小可执行文件大小的结果代码, -s从可执行文件中删除符号表和重定位信息。

有时候 – 如果需要小尺寸的话 – 玩弄不同的优化标志可能会 – 也可能不会 – 具有重要意义。 例如,切换-ffast-math和/或-fomit-frame-pointer有时可以节省数十个字节。

答案是-flto 。 你必须把它传递给你的编译和链接步骤,否则它不会做任何事情。

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

不幸的是,它似乎有点错误 – 我有东西没有正确构build的实例。 这可能是由于我正在使用的构build系统(QBS;这是非常新的),但无论如何我build议您只在可能的情况下为最终构build启用它,然后彻底testing构build。

在我看来,尼摩提供的答案是正确的。 如果这些说明不起作用,这个问题可能与您正在使用的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”声明的最重要的贡献。 如果你不能在你的平台上产生类似的减less,那么你的gcc版本可能不支持这个function。 我在Linux Mint 2.6.38-8-generic x86_64上使用gcc(4.5.2-8ubuntu4),ld(2.21.0.20110327)

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

标准库通过将所有的函数分割成独立的对象文件,然后用ar 。 如果你把结果档案作为一个库链接起来(例如把-l your_library选项给ld),那么ld将只包含实际使用的对象文件,因此也包括符号。

你也可以find一些类似的使用问题的答复。

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

更多信息(以及对于例如图书馆的细粒度方法)可在GCC wiki上获得 。

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

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

你可以在目标文件(例如可执行文件)上使用strip二进制来去除它的所有符号。

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