Jay*_*Jay 28 c size optimization linker gcc
以下在GCC页面中提到的功能部分和数据部分选项:
Run Code Online (Sandbox Code Playgroud)-ffunction-sections -fdata-sections如果目标支持任意节,则将每个函数或数据项放入输出文件中的自己的部分.函数名称或数据项名称确定输出文件中节的名称.在链接器可以执行优化以改善指令空间中引用的位置的系统上使用这些选项.大多数使用ELF对象格式的系统和运行Solaris 2的SPARC处理器都具有这种优化的链接器.AIX可能会在将来进行这些优化.
只有在获得重大好处时才使用这些选项.指定这些选项时,汇编器和链接器将创建更大的对象和可执行文件,并且速度也会更慢.如果指定此选项,则无法在所有系统上使用gprof,如果同时指定了此选项和-g,则可能无法进行调试.
我的印象是这些选项有助于减少可执行文件的大小.为什么这个页面会说它会创建更大的可执行文件?我错过了什么吗?
Ant*_*aaf 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;
}
Run Code Online (Sandbox Code Playgroud)
用于此测试的GCC版本是:
gcc version 6.1.1 20160526 (Arch Repository)
Run Code Online (Sandbox Code Playgroud)
首先,没有-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
Run Code Online (Sandbox Code Playgroud)
从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
Run Code Online (Sandbox Code Playgroud)
我们立即看到queue_empty的长度增加了4个字节到24个字节(18个十六进制),并且现在在queue_empty的文字池中有两个重定位.这些重定位对应于创建的两个BSS部分的地址,每个全局变量一个.这里需要有两个地址,因为编译器无法知道链接器最终将这两个部分放入的相对位置.查看queue_empty开头的指令,我们看到有一个额外的负载,编译器必须生成单独的加载对以获取该部分的地址,然后生成该部分中变量的值.此版本的queue_empty中的额外指令不会使函数的主体更长,它只需要以前是nop的点,但通常情况并非如此.
小智 13
您可以使用-ffunction-sections和-fdata-sections静态库,这将增加静态库的大小,因为每个函数和全局变量的数据将被放置在一个单独的部分.
然后使用-Wl,--gc-sections与此静态库链接的程序,这将删除未使用的部分.
因此,最终的二进制文件将更小,没有那些标志.
但要小心,因为-Wl,--gc-sections可以破坏事情.
添加一个额外的步骤并构建一个.a存档,我得到了更好的结果:
-ffunction-sections -fdata-sections标志一起使用.o对象都被放入一个.a存档中ar rcs file.a *.o-Wl,-gc-sections,-u,main选项调用链接器-Os。