以下在GCC页面中提到的功能部分和数据部分选项:
-ffunction-sections -fdata-sections
如果目标支持任意部分,则将每个函数或数据项放入输出文件中的自己的部分。函数名称或数据项名称确定输出文件中节的名称。 在链接器可以执行优化以改善指令空间中引用的位置的系统上使用这些选项。大多数使用ELF对象格式的系统和运行Solaris 2的SPARC处理器都具有这种优化的链接器。 AIX可能会在将来进行这些优化。
只有在获得重大好处时才使用这些选项。 当您指定这些选项时,汇编器和链接器将创建更大的对象和可执行文件,并且速度也会变慢。 如果您在所有系统上都无法使用gprof指定此选项,如果同时指定此选项和-g,则可能会出现调试问题。
我的印象是这些选项有助于减少可执行文件的大小。为什么这个页面会说它会创建更大的可执行文件?我错过了什么吗?
答案 0 :(得分:30)
有趣的是,使用-fdata-sections
可以创建函数的文字池,从而使函数本身更大。特别是我在ARM上注意到这一点,但在其他地方可能也是如此。我测试的二进制文件只增长了25%,但确实增长了。看看改变后的函数的反汇编,很明显为什么。
如果目标文件中的所有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 <queue_empty>:
0: 4b03 ldr r3, [pc, #12] ; (10 <queue_empty+0x10>)
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个十六进制),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 <queue_empty>:
0: 4b03 ldr r3, [pc, #12] ; (10 <queue_empty+0x10>)
2: 6818 ldr r0, [r3, #0]
4: 4b03 ldr r3, [pc, #12] ; (14 <queue_empty+0x14>)
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个十六进制),并且现在在queue_empty的文字池中有两个重定位。这些重定位对应于创建的两个BSS部分的地址,每个全局变量一个。这里需要有两个地址,因为编译器无法知道链接器最终将这两个部分放入的相对位置。查看queue_empty开头的指令,我们看到有一个额外的负载,编译器必须生成单独的加载对以获取该部分的地址,然后生成该部分中变量的值。这个版本的queue_empty中的额外指令不会使函数的主体更长,它只需要以前是nop的点,但通常情况并非如此。
答案 1 :(得分:24)
使用这些编译器选项时,可以添加将删除所有未使用代码的链接器选项-Wl,--gc-sections
。
答案 2 :(得分:13)
您可以在静态库上使用-ffunction-sections
和-fdata-sections
,这会增加静态库的大小,因为每个函数和全局数据变量都将放在一个单独的部分中。
然后在与此静态库链接的程序上使用-Wl,--gc-sections
,这将删除未使用的部分。
因此,如果没有这些标志,最终的二进制文件会更小。
但要小心,因为-Wl,--gc-sections
会破坏事情。
答案 3 :(得分:4)
我在添加额外步骤并构建.a
存档后获得了更好的结果:
-ffunction-sections
-fdata-sections
标志.o
个对象都会被放入.a
存档ar rcs file.a *.o
-Wl,-gc-sections,-u,main
选项-Os
。答案 4 :(得分:0)
我试了一会儿,看着结果看起来大小增加来自不同对齐的对象的顺序。 Normaly链接器对对象进行排序以保持它们之间的填充很小但看起来它只能在一个部分内工作,而不是在各个部分之间。因此,您经常在每个函数的数据部分之间获得额外的填充,从而增加整体空间。
对于带有-Wl,-gc-sections的静态库,删除未使用的部分很可能会为小幅增加提供更多。