我在一个极度受限的嵌入式内存环境中的经验教训:
有许多方法可以减少内存占用,我相信人们已经写过很多相关的书籍,但是其中一些主要的方法包括:
使用编译器选项来减小代码大小(包括 -Os 和打包/对齐选项)
使用链接器选项剥离无用代码
如果你从闪存(或 ROM)加载到 RAM 中执行(而不是直接从闪存中执行),则使用压缩的闪存映像,并使用启动加载程序进行解压缩。
使用静态分配:堆是分配有限内存的低效方式,并且如果受到约束可能会因碎片而失败。
使用工具找到堆栈高水位线(通常是用模式填充堆栈,执行程序,然后查看模式留在哪里),以便可以最优地设置堆栈大小
当然,还要优化算法以减少内存占用(通常以速度为代价)
const
声明常量数据表。这将避免数据从闪存复制到RAM。Unix哲学的一个规则可以帮助使代码更加紧凑:
表示法规则:将知识折叠到数据中,以便程序逻辑变得简单且健壮。
我已经看到了多少次复杂的分支逻辑,跨越许多页面,本可以折叠成一个漂亮的紧凑规则表、常量和函数指针。状态机经常可以用这种方式表示(状态模式)。命令模式也适用。这就是关于编程的声明性与命令性风格的区别。
记录事件代码和二进制数据,而不是纯文本。然后使用“短语书”重新构建事件消息。短语书中的消息甚至可以包含printf样式的格式说明符,以便事件数据值在文本中被整齐地显示。
reserve()
到正确的容量,不要让它们自动增长。如果需要频繁地分配/释放数据缓冲区(例如,用于通信包),则使用内存池。我曾经甚至扩展了C/C++运行时,以便在初始化序列之后,如果有任何东西尝试动态分配内存,则会中止我的程序。strings
命令,按长度排序结果,然后将最长的字符串注入到镜像中,重复此过程直到无聊得不得不去做更有趣的事情。虽然这不是C++,但我们确实忽略了混淆的函数名。 - Steve Jessop从链接器生成一个映射文件,它将显示内存分配的情况。这是优化内存使用的良好起点。它还将显示所有函数以及代码空间的布局。
这里有一本关于这个主题的书:Small Memory Software: Patterns for systems with limited memory。
(注意:该段已经是中文,无需翻译。)好的,大部分都已经被提到了,但这里是我的清单:
最后但同样重要的是,在追求最小可能的代码大小时,不要过度。同时注意性能和可维护性。过度优化的代码往往很快就会退化。
在其他人的建议之上:
限制使用C++功能,像使用ANSI C一样编写代码并增加一些扩展。标准(std::)模板使用了大量的动态内存分配。如果可能的话,应尽量避免使用模板。虽然它们本质上没有危害,但只需使用几个简单、干净、优雅的高级指令就可以生成大量机器代码。这鼓励以一种非常占用内存的方式编写代码,尽管存在所有“干净代码”的优点。
如果必须使用模板,则应编写自己的模板或使用专为嵌入式设备设计的模板,将固定大小作为模板参数传递,并编写测试程序,以便测试模板并检查-S输出以确保编译器不会生成可怕的汇编代码来实例化它。
手动对齐结构体,或使用#pragma pack。
{char a; long b; char c; long d; char e; char f; } //is 18 bytes,
{char a; char c; char d; char f; long b; long d; } //is 12 bytes.
出于同样的原因,使用集中的全局数据存储结构而不是分散的本地静态变量。
明智地平衡malloc()/new和静态结构的使用。
如果您需要给定库的子集功能,请考虑编写自己的库。
展开短循环。
for(i=0;i<3;i++){ transform_vector[i]; }
比
长。
transform_vector[0];
transform_vector[1];
transform_vector[2];
不要对较长的文件这样做。
将多个文件打包在一起,让编译器内联短函数并执行各种优化。链接器无法进行此操作。
首先,告诉你的编译器优化代码大小。GCC 使用-Os
标志进行此操作。
其他一切都在算法级别上 - 使用类似于查找内存泄漏的工具,但是要寻找可以避免的分配和释放。
还应该查看常用的数据结构打包方式 - 如果可以削减一两个字节,就可以大幅减少内存使用。