我有一个需要新功能的传统固件应用程序.应用程序的大小已经接近设备的有限闪存容量,并且少数新功能和变量将其推到了边缘.打开编译器优化可以解决这个问题,但客户对这样做很谨慎,因为它们过去曾导致过失败.那么,在重构C代码以产生较小的输出时,需要注意哪些常见的事情?
尽可能使用生成函数而不是数据表
禁用内联函数
将常用的宏转换为函数
降低大于本机大小的变量的分辨率(即8位微,尝试去除16和32位变量 - 一些代码序列的两倍和四倍)
如果micro具有较小的指令集(Arm thumb),则在编译器中启用它
如果内存是分段的(即分页或非线性)那么
重新排列代码,以便需要使用更少的全局调用(更大的调用指令)
重新排列代码和变量用法以消除全局内存调用
重新评估全局内存使用情况 - 如果它可以放在堆栈上那么好了
确保你在关闭调试时进行编译 - 在某些处理器上它会产生很大的不同
压缩无法动态生成的数据 - 然后在启动时解压缩为ram以便快速访问
深入研究编译器选项 - 可能每个调用都是自动全局的,但您可以在逐个文件的基础上安全地禁用它以减小大小(有时显着)
如果您仍然需要更多空间而不是compile with optimizations
打开,那么请查看生成的程序集与未优化的代码.然后重新编写发生最大变化的代码,以便编译器根据棘手的C重写生成相同的优化,并关闭优化.
例如,您可能有几个"if"语句进行类似的比较:
if(A && B && (C || D)){} if(A && !B && (C || D)){} if(!A && B && (C || D)){}
然后创建一个新变量并提前进行一些比较将使编译器免于复制代码:
E = (C || D); if(A && B && E){} if(A && !B && E){} if(!A && B && E){}
如果打开它,这是编译器为您自动执行的优化之一.还有很多很多其他的,如果你想学习如何在C代码中手工完成,你可以考虑阅读一些编译器理论.
通常:利用您的链接器映射或工具来确定最大/最多的符号是什么,然后可能使用反汇编程序查看它们.你会发现这种方式会让你感到惊讶.
使用perl或类似的东西,您可以缩短.xMAP文件或"objdump"或"nm"的结果,并以相应信息的各种方式重新排序.
特定于小指令集:观察文字池使用情况.虽然从例如ARM(每条指令32位)指令集改变为THUMB(每条指令16位)指令集在某些ARM处理器上可能很有用,但它会减小"立即"字段的大小.
突然间,从全局或静态直接加载的东西变得非常间接; 它必须首先将全局/静态的地址加载到寄存器中,然后从中加载,而不是直接在指令中编码地址.因此,您可以在文字池中获得一些额外的指令和额外的条目,这些条目通常是一条指令.
解决这个问题的策略是将全局和静态组合成结构; 这样,您只存储一个文字(全局结构的地址)并从中计算偏移量,而不是在访问多个静态/全局变量时存储许多不同的文字.
我们将"单例"类从管理它们自己的实例指针转换为仅仅是大型"struct GlobalTable"中的成员,并且在某些情况下它在代码大小(百分之几)和性能方面产生了明显的差异.
否则:密切关注静态结构和非平凡构造数据的数组.这些中的每一个通常都会生成大量的.sinit代码("隐藏函数",如果你愿意的话),它们在main()之前运行,以正确填充这些数组.如果你只能在静力学中使用琐碎的数据类型,那么你会好得多.
这又是通过在"nm"或"objdump"等的结果上使用工具可以容易地识别的东西.如果你有很多.sinit的东西,你会想要调查!
哦,并且 - 如果你的编译器/链接器支持它,不要害怕选择性地为某些文件或函数启用优化或更小的指令集!