文章目录
  1. 1. 为什么没调用的函数也链接进来?
  2. 2. 简单方案:用编译选项–split_sections
  3. 3. 首选方案:用feedback文件,编译和链接选项都加上:–feedback fb.txt
  4. 4. feedback文件的内容
  5. 5. 使用效果
  6. 6. 过程解析
  7. 7. 图形界面里的设置
  8. 8. 后记

为什么没调用的函数也链接进来?

编译器通常将一个源文件编译成一个object文件(*.o),每个object里的所有函数归到一个section里。链接时,只要一个section中有一个函数被引用了,整个section都会被链接进目标文件中。也就是说链接是以section为单位,而不是以函数为单位。

简单方案:用编译选项–split_sections

这个方案将源文件里的每个函数放到一个单独的section里,这样没调用的函数就不会被链接进去了。缺点是用到的函数可能会稍微增加代码的大小,相比因去掉的函数省掉的空间还是可以接受的。
相关介绍可以见Keil的帮助文档中的Compiler User Guide里split_sections有关条目。

首选方案:用feedback文件,编译和链接选项都加上:–feedback fb.txt

原理:链接时生成一个feedback文件,注明哪些函数没有被调用,下次链接时引用这个feedback文件,将未调用的函数与调用了的函数分开,单独放到一个secton里这样链接时就不会链接进去了。

feedback文件的内容

除了文件头表明是feedback文件和生成日期,其它标明某个object文件里哪些函数引用次数为0。以下截取一小段做示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
;#<FEEDBACK># ARM Linker, 5050106: Last Updated: Tue May 05 10:58:35 2015
;VERSION 0.2
;FILE lcd.o
LcmPutChar_r <= USED 0
LcmPutHZ_r <= USED 0
;FILE stm32f10x_gpio.o
GPIO_AFIODeInit <= USED 0
GPIO_DeInit <= USED 0
GPIO_ETH_MediaInterfaceConfig <= USED 0
GPIO_EXTILineConfig <= USED 0
GPIO_EventOutputCmd <= USED 0
GPIO_EventOutputConfig <= USED 0
GPIO_Init <= USED 0
GPIO_PinLockConfig <= USED 0
GPIO_PinRemapConfig <= USED 0
GPIO_ReadInputData <= USED 0
GPIO_ReadInputDataBit <= USED 0
GPIO_ReadOutputData <= USED 0
GPIO_ReadOutputDataBit <= USED 0
GPIO_ResetBits <= USED 0
GPIO_SetBits <= USED 0
GPIO_StructInit <= USED 0
GPIO_Write <= USED 0
GPIO_WriteBit <= USED 0

使用效果

自己STM32F103项目使用feedback前后编译结果大小对比:
Program Size: Code=36756 RO-data=9432 RW-data=612 ZI-data=15396
Program Size: Code=27204 RO-data=9356 RW-data=592 ZI-data=15392

过程解析

需要编译连接两次,第一次更新feedback,第二次应用新的feedback。不过在正常开发过程中,每次编译程序结构变化不会太大,可以直接利用前一次链接的结果。过程解析如下:
1 第1次编译,因为没找到feedback文件,会报warning,并按缺省方案将每一个源文件的函数编译到一个section。
2 第1次链接,正常链接,链接完了分析函数调用情况,生成feedback文件标明哪些函数未调用。
3 第2次编译,引用feedback文件,将未调用的函数单独放到一个section。
4 第2次链接,正常链接,未调用的函数被忽略。生成新的feedback文件,因为程序没有变化,跟前一次生成的内容是一样的。
5 修改程序,假设函数调用关系没变化。
6 第3次编译,虽然引用的是旧的feedback文件,由于函数调用关系没变,没有影响。
7 第3次链接,正常链接,未调用的函数被忽略。生成新的feedback文件,因为函数调用关系没有变化,跟前一次生成的内容仍是一样的。
8 修改程序,假设函数调用情况有变化。
9 第4次编译,按旧的feedback文件生成object文件。
10 第4次链接,可能不是最优,运行是没问题的,调试也没啥影响。有必要的话用刚生成的新feedback文件再编译一次就是了。

图形界面里的设置

–split_sections对应Option对话框C/C++子页里的One ELF Section per function。
–feedback对应Target子页的Use Cross-Module Optimization。但拿自己的项目测试不知道为什么编译了3次(有人说2次)。而且没修改任何源文件点Build,又整个项目重新编译3次,急死人。还是别勾那个Use Cross-Module Optimization,自己在C/C++和Linker的Misc controls添加feedback选项吧。

后记

在amobbs里讨论时有人问为什么会增加代码大小,我粗浅的理解:C转成机器码后一般都是一段code加一小段数据,这些数据是code里引用的地址、常量等。看看反汇编的代码总会看到加载一个用PC+偏移量寻址的数据。如果几个关系比较紧密的函数放在一个源文件里,链接出来的代码中部分地址、常量可以共用。如果用split_sections将函数都链接到独立的section里,这些数据自然就无法共用了,代码就会增加几个或几十个字节。用feedback就不存在这个问题。

两个方案不要一起用。因为用了split_sections,所有函数都拆开了,feedback还有啥用呢?

文章目录
  1. 1. 为什么没调用的函数也链接进来?
  2. 2. 简单方案:用编译选项–split_sections
  3. 3. 首选方案:用feedback文件,编译和链接选项都加上:–feedback fb.txt
  4. 4. feedback文件的内容
  5. 5. 使用效果
  6. 6. 过程解析
  7. 7. 图形界面里的设置
  8. 8. 后记