有趣的是,使用-fdata-sections 可以使您的函数的字面量池,从而使您的函数本身更大。我特别在 ARM 上注意到了这一点,但在其他地方可能也是如此。我正在测试的二进制文件只增长了四分之一个百分点,但它确实增长了。查看已更改功能的反汇编,原因就很清楚了。
如果目标文件中的所有 BSS(或 DATA)条目都分配给单个部分,则编译器可以将该部分的地址存储在函数字面量池中,并使用函数中该地址的已知偏移量生成加载访问您的数据。但是,如果您启用-fdata-sections,它会将每条 BSS(或 DATA)数据放入其自己的部分,并且由于它不知道这些部分中的哪些可能会在以后被垃圾收集,或者链接器将按什么顺序放置所有这些部分进入最终的可执行映像,它不能再使用来自单个地址的偏移量加载数据。因此,它必须为每个使用的数据在文字池中分配一个条目,一旦链接器弄清楚最终图像中的内容和位置,它就可以使用实际地址修复这些文字池条目数据。
所以是的,即使使用-Wl,--gc-sections,生成的图像也会更大,因为实际的函数文本更大。
下面我添加了一个小例子
下面的代码足以看到我正在谈论的行为。请不要被 volatile 声明和全局变量的使用所迷惑,这两者在实际代码中都是有问题的。在这里,它们确保在使用 -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 hex),arm-none-eabi-objdump 的输出显示函数末尾有一个重定位字,它是 BSS 部分的地址(未初始化数据的部分)。函数中的第一条指令将该值(BSS 的地址)加载到 r3 中。接下来的两条指令相对于 r3 加载,分别偏移 0 和 4 个字节。这两个载荷是head和tail值的载荷。我们可以在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 的位置,但一般情况下不会这样。