代码之家  ›  专栏  ›  技术社区  ›  Jay

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

  •  28
  • Jay  · 技术社区  · 14 年前

    在GCC页面中,功能部分和数据部分选项如下所述:

    -ffunction-sections
    -fdata-sections
    

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

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

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

    5 回复  |  直到 10 年前
        1
  •  25
  •   Danijel    6 年前

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

        2
  •  30
  •   Anton Staaf    8 年前

    有趣的是,使用 -fdata-sections 可以使函数的文字池变大,从而使函数本身变大。我在手臂上特别注意到了这一点,但在其他地方可能是真的。我测试的二进制代码只增长了0.25%,但确实增长了。看看这些改变了的函数的反汇编,很清楚为什么。

    如果对象文件中的所有BSS(或数据)项都分配给单个节,那么编译器可以将该节的地址存储在函数文字池中,并生成与函数中该地址具有已知偏移量的加载以访问数据。但是如果你能 -fdata部分 它将每个BSS(或数据)数据放在自己的节中,并且由于它不知道这些节中的哪一个稍后可能被垃圾收集,或者链接器将这些节放在最终的可执行映像中的顺序,因此它不能再使用单个地址的偏移量加载数据。因此,它必须为每个使用的数据在文本池中分配一个条目,一旦链接器确定了进入最终图像的内容和位置,它就可以使用数据的实际地址来修复这些文本池条目。

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

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

    下面的代码足以看到我所说的行为。请不要被volatile声明和全局变量的使用所抛弃,这两者在实际代码中都是有问题的。在这里,它们确保在使用-fdata节时创建两个数据节。

    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部分,我们将得到以下结果。

    > 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 我们看到队列是20字节长(14个十六进制),并且 arm-none-eabi-objdump 输出显示在函数的末尾有一个重定位字,它是BSS部分(未初始化数据的部分)的地址。函数中的第一条指令将该值(BSS的地址)加载到r3中。下两条指令相对于r3加载,分别偏移0和4字节。这两种载荷是头部和尾部的载荷值。我们可以在输出的第一列中看到 无臂eabi nm . 这个 nop 函数的最后是字对齐文字池的地址。

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

    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节的地址,每个BSS节对应一个全局变量。这里需要有两个地址,因为编译器无法知道链接器最后将这两个部分放入的相对位置。看一下queue_empty开头的指令,我们发现有一个额外的加载,编译器必须生成单独的加载对,以获取节的地址,然后获取该节中变量的值。这个版本的queue_empty中的额外指令不会使函数体变长,它只是占用了以前是nop的位置,但一般情况下不会这样。

        3
  •  13
  •   fwhacking    13 年前

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

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

    因此,最终的二进制数将小于没有这些标志的t。

    尽管如此,还是要小心 -Wl,--gc剖面图 能打破一切。

        4
  •  4
  •   Rei Vilo    10 年前

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

    1. 首先,gcc和g++与 -ffunction-sections -fdata-sections 旗帜
    2. 那么,所有人 .o 对象被放入 .a.公司 存档方式 ar rcs file.a *.o
    3. 最后,使用 -Wl,-gc-sections,-u,main 选项
    4. 总之,优化设置为 -Os .
        5
  •  0
  •   Goswin von Brederlow    9 年前

    我试了一会儿,看看结果,似乎大小的增加来自于不同排列的对象的顺序。正常情况下,链接器对对象进行排序以保持它们之间的填充很小,但看起来这只在一个部分内有效,而不是在各个部分之间有效。因此,经常会在每个函数的数据段之间获得额外的填充,从而增加总体空间。

    对于带有-Wl,-gc段的静态lib,删除未使用的段很可能会弥补较小的增加。