Arm-GCC 代码瘦身教程

最近项目中用到一个Flash非常小的MCU,在这款芯片上开发程序就一定要注意程序最终二进制代码的大小,于是各种代码缩减的技能都要搞明白。

代码的瘦身,第一步想到的就是要从最终二进制中出去一些不用的函数和变量,这个说起来应该比较简单,似乎应该是一个编译器默认开启的功能,然而实际编译器并不会进行这种代码缩减,而是会将所有出现过的函数统统放到最终二进制文件中去,这样如果我们代码中有用不到的legacy的代码,这些代码就会增加二进制文件的大小。

直接祭出神器

废话不多说,直接祭出神器吧,虽然也是找了好久才找到的,其实实现的方法比较简单,就是在编译和链接的命令上加一个-flto的参数,lto的含义是Link Time Optimize也就是链接时优化,所以加上这个参数之后,编译器和连接器会自动去掉一些没有调用的函数和没有用到的变量。

神器也不是万能的

加上神器之后代码直接减少了一半,这个真的是挺给力的,了呵呵的下进去一看,程序直接跑飞,我擦怎么回事,二进制代码直接打开看看,不会是下载的问题吧。

这是编译出来和下载后的二进制文件,下载没问题,代码有问题。

00000000: 014b 1b78 7047 c046 04ed 00e0 c046 c046  .K.xpG.F.....F.F
00000010: 754b 764c 9a68 9a60 2022 1968 0a43 8021  uKvL.h.` ".h.C.!
00000020: 1a60 734a 5a60 1a68 8a43 1a60 7149 724a  .`sJZ`.h.C.`qIrJ
00000030: 724b d150 0022 7249 724b 5b1a 9b10 9342  rK.P."rIrK[....B
00000040: 00d0 c5e0 0020 7049 704b cb1a 9b10 013b  ..... pIpK.....;
00000050: 00d3 c2e0 8022 6e4b d205 996a 8020 1143  ....."nK...j. .C
00000060: 9962 d96a 4004 1143 d962 196b 1143 1963  [email protected]
00000070: 596b 1143 5963 996b 0a43 9a63 2422 654b  Yk.CYc.k.C.c$"eK
00000080: 6549 da60 1d3a 5a60 1a00 0b00 5468 0442  eI.`.:Z`....Th.B
00000090: fcd0 a022 d200 8858 604c 2040 8850 a120  ..."...X`L @.P.
000000a0: 0124 c000 0c50 5e48 5e4c 0c50 8c58 5e48  .$...P^H^L.P.X^H
000000b0: 2043 8850 8021 4904 9858 0842 fcd0 8020   C.P.!I..X.B...
000000c0: 8021 5a4a 5a4c 5a61 5a4a 8000 9a61 5a69  .!ZJZLZaZJ...aZi
000000d0: 594a 8904 2061 1160 4022 584d e127 6e68  YJ.. a.`@"XM.'nh
000000e0: 7f02 3243 6a60 8122 a062 9200 9c58 544d  ..2Cj`.".b...XTM
000000f0: 544e 2c40 9c50 c024 9d58 a400 2c43 9c50  TN,@.P.$.X..,C.P
00000100: d423 514a 5b00 d458 504d 2c40 d450 d458  .#QJ[..XPM,@.P.X
00000110: 0525 2143 d150 8021 d458 c905 0c43 d450  .%!C.P.!.X...C.P

正常二进制文件头:

00000000: 0018 0020 2104 0000 1104 0000 1104 0000  ... !...........
00000010: 1104 0000 1104 0000 1104 0000 1104 0000  ................
00000020: 1104 0000 1104 0000 1104 0000 1104 0000  ................
00000030: 1104 0000 1104 0000 1104 0000 1104 0000  ................
00000040: 1104 0000 1104 0000 1104 0000 1104 0000  ................
00000050: 1104 0000 1104 0000 1104 0000 1104 0000  ................
00000060: 1104 0000 1104 0000 1104 0000 1104 0000  ................
00000070: 1104 0000 1104 0000 1104 0000 1104 0000  ................
00000080: 1104 0000 1104 0000 1104 0000 1104 0000  ................
00000090: 1104 0000 1104 0000 1104 0000 1104 0000  ................
000000a0: 1104 0000 1104 0000 1104 0000 1104 0000  ................

可以看到正常二进制开头是比较整齐的中断向量表,那么看起来是中断向量表被优化掉了,哎,优化有风险。我们看看生成的map

00000000 g       .isr_vector    00000000 __vector_table
0000097c  w    F .text  00000002 .hidden __aeabi_ldiv0
00000694 g     F .text  00000000 .hidden __aeabi_uidiv
1ffff800 g       .heap  00000000 __end__
000007a8 g     F .text  000001cc .hidden __divsi3
1ffffc00 g       .heap  00000000 __heap_end
20001800 g       .heap  00000000 __StackTop
1ffff800 g       .heap  00000000 __heap_start
000007a0 g     F .text  00000008 .hidden __aeabi_uidivmod
20001400 g       *ABS*  00000000 __StackLimit
00000400 g       *ABS*  00000000 Heap_Size
0000097c  w    F .text  00000002 .hidden __aeabi_idiv0
00000974 g     F .text  00000008 .hidden __aeabi_idivmod
00000400 g       *ABS*  00000000 Stack_Size

可以看到中断向量表大小为0,真的是优化了。

搞定神器

没办法,网上找来找去,发现可以通过加__attribute__ ((used))来避免编译器优化,但是我加上了没有用,中断向量表依然被优化,就在快要放弃的时候,偶然发现这个只能用于static类型的变量,于是将中断向量表的数组改为static再次编译搞定,这个问题比较隐晦,查了好多资料才找到原因。

conststatic的区别

实际上一直出问题的主要原因是之前isr数组一直是用const修饰的,而__attribute__ ((used))的作用范围只针对static,好麻烦哈,所以之前各种尝试都没能搞定,最后才发现要改成static,这里也大概研究一下这两个修饰符之间的差异。

const: 它的含义是告诉编译器,这个变量是不可变的,只是说内容不可变,但是变量的空间是可以回收的,也就是一旦编译器认定这个变量不能访问就可以回收其空间,而一旦牵扯到回收空间,那么他的位置可能是SRAM,不过变量的初始值还是应该在Flash,当然也可能直接放到flash空间,在这里const没有起作用的关键还是编译器认定中断向量表用不到,可以直接删掉。。

static: 静态变量,它的含义就比较单纯,就是告诉编译器这个变量会一直占用空间,并且内容不会变化,所以这种变量一般都是直接放到flash中去的。


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

发表新评论