C 有哪些重构方法可以减少编译代码的大小?

C 有哪些重构方法可以减少编译代码的大小?,c,optimization,memory,embedded,size,C,Optimization,Memory,Embedded,Size,我有一个需要新功能的旧固件应用程序。应用程序的大小已经接近设备有限的闪存容量,而新的功能和变量很少,这就把它推到了边缘。启用编译器优化可以做到这一点,但客户对此很谨慎,因为它们在过去曾导致过失败。那么,当重构C代码以产生较小的输出时,需要寻找哪些常见的东西呢?重构应该对程序的内存占用有最大的影响。 尽可能使用生成函数而不是数据表 禁用内联函数 将常用宏转换为函数 降低大于本机大小的变量的分辨率(即,8位micro,尝试去除16位和32位变量-某些代码序列的两倍和四倍) 如果micro具有较小的指

我有一个需要新功能的旧固件应用程序。应用程序的大小已经接近设备有限的闪存容量,而新的功能和变量很少,这就把它推到了边缘。启用编译器优化可以做到这一点,但客户对此很谨慎,因为它们在过去曾导致过失败。那么,当重构C代码以产生较小的输出时,需要寻找哪些常见的东西呢?

重构应该对程序的内存占用有最大的影响。

  • 尽可能使用生成函数而不是数据表
  • 禁用内联函数
  • 将常用宏转换为函数
  • 降低大于本机大小的变量的分辨率(即,8位micro,尝试去除16位和32位变量-某些代码序列的两倍和四倍)
  • 如果micro具有较小的指令集(Arm thumb),请在编译器中启用它
  • 如果内存是分段的(即分页的或非线性的),那么
    • 重新排列代码,以便使用更少的全局调用(更大的调用指令)
    • 重新安排代码和变量使用以消除全局内存调用
    • 重新评估全局内存使用情况-如果可以将其放在堆栈上,那就更好了
  • 确保您在编译时关闭了调试-在某些处理器上,这会产生很大的差异
  • 压缩不能动态生成的数据,然后在启动时解压缩到ram中,以便快速访问
  • 深入研究编译器选项——可能每个调用都是自动全局的,但您可以逐个文件安全地禁用该选项,以减小大小(有时会显著减小)
如果仍然需要比启用优化编译时更多的空间,请查看生成的程序集与未优化的代码。然后在发生最大更改的地方重新编写代码,以便编译器在关闭优化的情况下基于复杂的C重新编写生成相同的优化

例如,您可能有几个“如果”语句进行类似的比较:

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代码中手工学习这一点,你可以考虑读一点编译器理论。

注意宏。它们可以从一个宏扩展生成大量代码。如果您发现这样的宏,请尝试重写它们,使其大小最小化,并将功能移动到功能

注意重复代码-复制粘贴和逻辑复制。尝试将重复的代码分离为函数


检查编译器是否支持内联,是否可以将其关闭。

触发错误的编译器优化?真奇怪。 获取程序的映射图,并查看是否应以数据或代码为目标。 查找重复的代码。寻找具有类似目标的代码。busybox代码就是一个例子,它旨在减少内存占用


它更倾向于大小而不是可读性,因此有时会变得非常难看,比如gotos等等。

一般情况下:利用链接器映射或工具找出最大/最多的符号是什么,然后可能使用反汇编程序查看它们。你会对你这样的发现感到惊讶

使用一些perl之类的工具,您可以对.xMAP文件或“objdump”或“nm”的结果进行简短的处理,并以各种方式对其重新排序以获取相关信息


针对小型指令集:注意使用情况。例如,在某些ARM处理器上,从ARM(每条指令32位)指令集更改为THUMB(每条指令16位)指令集可能很有用,但这会减小“立即数”字段的大小

突然,来自全局或静态的直接负载变得非常间接;它必须首先将全局/静态的地址加载到寄存器中,然后从寄存器加载,而不是直接在指令中编码地址。因此,对于通常是一条指令的内容,您可以在文本池中获得一些额外的指令和一个额外的条目

解决这个问题的一个策略是将全局变量和静态变量组合到一个结构中;这样,您只存储一个文本(全局结构的地址)并计算其偏移量,而不是在访问多个static/global时存储许多不同的文本

我们将我们的“单例”类从管理自己的实例指针转换为只作为大型“struct GlobalTable”中的成员,在某些情况下,它在代码大小(百分之几)和性能方面产生了显著的差异


否则:请注意非平凡构造数据的静态结构和数组。其中的每一个都会生成大量的.sinit代码(“不可见函数”,如果您愿意的话),这些代码在main()之前运行,以正确填充这些数组。如果您只能在静态中使用琐碎的数据类型,那么您的情况会好得多

这也是可以通过使用工具在“nm”或“objdump”等结果上轻松识别的东西。如果你有一大堆邪恶的东西,你会想调查的


哦,还有——如果您的编译器/链接器支持它,不要害怕为某些文件或函数选择性地启用优化或更小的指令集

上述答案声称“启用编译器优化[减少代码大小]”。鉴于我在嵌入式系统TI DSP编程方面的所有文档和经验,我知道启用优化将增加代码大小(对于TI DSP芯片)


void foo(char* str,uint8_t length){
char local_string[length];
....
}
void foo(char* str,uint8_t length){
char local_string[MAXIMUM_LENGTH];
....
}