嵌入式编程节约内存技巧
1.写在前面
嵌入式开发,不同于PC、移动设备(手机、平板)以“GB”为单位的存储空间(ROM)和内存(RAM)。尤其是MCU、SOC,RAM是KB级别的容量,ROM则稍微较大;当然也有较大容量的MCU选择,成本也跟着上去。MCU类的产品出货量大,成本控制比较苛刻,所以在选择MCU时,老板当然希望占用资源越小越好,进而控成本。因此,我们在程序编码时,就需要考虑资源问题,甚至要使用上一些“手段”来控制,平衡资源和效率,尽可能降低固件占用资源同时保证效率。
2.一些技巧
程序界有句比较熟悉的话:“时间换空间、空间换时间”,字面意思就是资源空间和程序执行时间的权衡。比如,数据放在ROM中和RAM中时,两者的读取速度就不一样。本文目的是“节约内存”为出发点,所以对于一些处理技巧上,效率肯定会打折扣,这就是一个“权衡”问题,需根据具体实际应用场合来选择。
[1] ROM代替RAM
前面提到,MCU的ROM往往远大于RAM,而且ROM和RAM的成本6:1,常量数据可以存在在ROM中。常规操作就是加“const”修饰。
[2] 用位域
对于一些变量并未使用完整个数据类型范围,可以使用位域,未使用到的位留给其他使用。如1字节变量就可以实现8个bool型操作。位域常用在结构体中。
struct type
{
uint8_t select : 1;
uint8_t calc : 7;
};
[3] 传递指针(地址)
对于MCU裸机编程或者带RTOS(实时系统)编程,虽然没有明确的驱动、应用分层界限,但为了提高程序可移植性和可读性,也会做一些分层处理。底层驱动提供的接口应该是以指针(地址)方式作为形参,而且中间尽量避免一些内存拷贝。即,最终的内存空间应是由应用层申请的。
uint8_t driver_fun(void *buf);
uint8_t app_fun()
{
uint8_t buf[10];
driver_fun(buf);
}
[4] 逐个处理
对于处理,可以将某段内存数据首地址直接传递到调用函数,由函数内部实现内存偏移处理。换一个方式处理,也可以单个数据传入,牺牲效率以换取内存。最常见的情况有:
1)串口编码发送。比如用户端发送“abcdef”,实际需编码成“12abcdef34”发送。如果单个字符发送则可以节约中间编码时临时内存拷贝的开销,但效率明显降低。
2)串口接收。接收组串口字符数据并解析,单字节读读取缓存数据并解析,解析完则处理;比获取一定长度的数据再解析要节约一段内存。
[5] 共享内存
多个任何函数使用一段内存,可以进行“分时复用”。
[6] 内存池
对于嵌入式开发,特别是MCU类编程,一般不建议使用malloc/new等动态内存分配,因为多次申请释放过程会产生内存碎片,而MCU类内存并不大,容易导致内存申请不失败导致程序异常甚至崩溃。如果内存实在吃紧,可以使用“内存池”。
内存池,在操作系统中很常见,我们也可以自己实现一套简易的内存池算法。内存池基本原理就是以链表的结构实现一定数量、相同大小的内存块,使用时从该内存池中申请指定大小的内存块,使用完释放,效率提升并降低碎片化。内存池只是降低碎片化,非完全杜绝,一般情况下,MCU类编程少用动态内存,严格场合不用。
[7] 编译器优化
这个是编译器选项的设置,对于自身代码有足够的信心,可以尽可能使用编译器的高优化选项。,这项慎用,代码不严谨易导致一些异常问题。