查询gcc的-ffunction-section和-fdata-sections选项

以下在GCC页面中提到的function部分和数据部分选项:

-ffunction-sections -fdata-sections 

如果目标支持任意节,则将每个函数或数据项放入输出文件中的自己的部分。 函数名称或数据项名称确定输出文件中节的名称。 在链接器可以执行优化以改善指令空间中引用的局部性的系统上使用这些选项。 大多数使用ELF对象格式的系统和运行Solaris 2的SPARC处理器都具有这种优化的链接器。 AIX可能会在将来进行这些优化。

只有在这样做的重大好处时才使用这些选项。 指定这些选项时,汇编器和链接器将创建更大的对象和可执行文件,并且速度也会更慢。 如果指定此选项,则无法在所有系统上使用gprof,如果同时指定此选项和-g,则可能在调试时遇到问题。

我的印象是这些选项有助于减少可执行文件的大小。 为什么这个页面会说它会创建更大的可执行文件? 我错过了什么吗?

使用这些编译器选项时,可以添加链接器选项-Wl,--gc-sections ,这将删除所有未使用的代码。

有趣的是,使用-fdata-sections可以创建函数的文字池,从而使您的函数本身更大。 特别是我在ARM上注意到了这一点,但在其他地方可能也是如此。 我测试的二进制文件只增长了25%,但确实增长了。 看看改变后的function的反汇编,很明显为什么。

如果目标文件中的所有BSS(或DATA)条目都分配给单个部分,则编译器可以将该部分的地址存储在函数文本池中,并使用函数中的该地址生成具有已知偏移的加载来访问数据。 但是如果启用-fdata-sections它会将每个BSS(或DATA)数据放入其自己的部分,并且因为它不知道以后哪些部分可能被垃圾收集,或者链接器将放置所有部分的顺序将这些部分放入最终的可执行映像中,它无法再使用来自单个地址的偏移量来加载数据。 所以相反,它必须在每个使用的数据的文字池中分配一个条目,并且一旦链接器找出了进入最终图像的内容以及在哪里,那么它可以用实际的地址来修复这些文字池条目。数据。

所以是的,即使使用-Wl,--gc-sections ,结果图像也可能更大,因为实际的函数文本更大。

下面我添加了一个最小的例子

下面的代码足以看到我正在谈论的行为。 请不要被挥发性声明和全局变量的使用抛弃,这两者在实际代码中都是有问题的。 在这里,当使用-fdata-sections时,它们确保创建两个数据部分。

 static volatile int head; static volatile int tail; int queue_empty(void) { return head == tail; } 

用于此测试的GCC版本是:

 gcc version 6.1.1 20160526 (Arch Repository) 

首先,没有-fdata-sections,我们得到以下内容。

 > arm-none-eabi-gcc -march=armv6-m \ -mcpu=cortex-m0 \ -mthumb \ -Os \ -c \ -o test.o \ test.c > arm-none-eabi-objdump -dr test.o 00000000 : 0: 4b03 ldr r3, [pc, #12] ; (10 ) 2: 6818 ldr r0, [r3, #0] 4: 685b ldr r3, [r3, #4] 6: 1ac0 subs r0, r0, r3 8: 4243 negs r3, r0 a: 4158 adcs r0, r3 c: 4770 bx lr e: 46c0 nop ; (mov r8, r8) 10: 00000000 .word 0x00000000 10: R_ARM_ABS32 .bss > arm-none-eabi-nm -S test.o 00000000 00000004 b head 00000000 00000014 T queue_empty 00000004 00000004 b tail 

arm-none-eabi-nm我们看到queue_empty是20个字节长(14个hex), arm-none-eabi-objdump输出显示函数末尾有一个重定位字,它是地址BSS部分(未初始化数据部分)。 函数中的第一条指令将该值(BSS的地址)加载到r3中。 接下来的两条指令相对于r3加载,分别偏移0和4字节。 这两个载荷是头部和尾部的载荷。 我们可以在arm-none-eabi-nm的输出的第一列中看到这些偏移。 函数末尾的nop是对齐文字池的地址。

接下来我们将看到添加-fdata-sections时会发生什么。

 arm-none-eabi-gcc -march=armv6-m \ -mcpu=cortex-m0 \ -mthumb \ -Os \ -fdata-sections \ -c \ -o test.o \ test.c arm-none-eabi-objdump -dr test.o 00000000 : 0: 4b03 ldr r3, [pc, #12] ; (10 ) 2: 6818 ldr r0, [r3, #0] 4: 4b03 ldr r3, [pc, #12] ; (14 ) 6: 681b ldr r3, [r3, #0] 8: 1ac0 subs r0, r0, r3 a: 4243 negs r3, r0 c: 4158 adcs r0, r3 e: 4770 bx lr ... 10: R_ARM_ABS32 .bss.head 14: R_ARM_ABS32 .bss.tail arm-none-eabi-nm -S test.o 00000000 00000004 b head 00000000 00000018 T queue_empty 00000000 00000004 b tail 

我们立即看到queue_empty的长度增加了4个字节到24个字节(18个hex),并且现在在queue_empty的文字池中有两个重定位。 这些重定位对应于创建的两个BSS部分的地址,每个全局变量一个。 这里需要有两个地址,因为编译器无法知道链接器最终将这两个部分放入的相对位置。查看queue_empty开头的指令,我们看到有一个额外的负载,编译器必须生成单独的加载对以获取该部分的地址,然后生成该部分中变量的值。 这个版本的queue_empty中的额外指令不会使函数的主体更长,它只是占用以前是nop的点,但通常情况并非如此。

您可以在静态库上使用-ffunction-sections-fdata-sections ,这将增加静态库的大小,因为每个函数和全局数据变量将放在一个单独的部分中。

然后在与此静态库链接的程序上使用-Wl,--gc-sections ,这将删除未使用的部分。

因此,最终的二进制文件将更小,没有那些标志。

但要小心,因为-Wl,--gc-sections可以破坏事物。

我得到更好的结果添加一个额外的步骤并构建.a存档:

  1. 首先,gcc和g ++与-ffunction-sections -fdata-sections标志一起使用
  2. 然后,将所有.o对象放入带有ar rcs file.a *.o.a存档中
  3. 最后,使用-Wl,-gc-sections,-u,main选项调用链接器
  4. 总而言之,优化设置为-Os

我试了一会儿,看着结果看起来大小增加来自不同对齐的对象的顺序。 Normaly链接器对对象进行排序以保持它们之间的填充较小,但看起来它只能在一个部分内工作,而不是在各个部分之间。 因此,您经常在每个函数的数据部分之间获得额外的填充,从而增加整体空间。

对于带有-Wl,-gc-sections的静态库,删除未使用的部分很可能会使小幅增加。