IAR编译器如何节省代码占用的flash空间?

简介: IAR编译器如何节省代码占用的flash空间

IAR编译器如何节省代码占用的flash空间

最近一直在做一个项目,用的IAR的开发环境,芯片的空间flash大小是32kB。在做项目之前评估代码量感觉够用,因此切换平台时选用了32kB的M0+核芯片。可是代码不是你想多大就多大。
所以问题就来了,当芯片的flash空间不足以支撑你的代码量时,如何通过编译优化等等各种手段缩小代码的占用空间呢?

常用的手段有以下几种:

  • 优化代码
  • 尝试编译器编译优化
  • 更改芯片默认配置,最大限度利用芯片空间——扩大芯片可用空间!

本文主要针对第三种手段说一下如何在芯片层面上“扩大空间”。

优化代码

这一点需要根据你的代码实际情况来优化,删除一下冗余的代码片段、尝试一个接口函数兼容多种类似功能、更改循环逻辑等等。这个需要自己对代码有深刻的理解。

尝试编译器优化

这一点说实话有风险。以我用的IAR编译器为例,有以下四种优化等级:

image.png

自己实测后发现,不优化和最高等级优化编译后的代码量还是有很大区别的。但是自己测试发现最高优化程序运行没有问题。但是有同事遇到过最高优化后程序无法正常运行的情况。因此,采用何种优化等级,需要自己去测试。这一点涉及到编译器的知识,不多赘述。

更改芯片默认配置,最大限度利用芯片空间——扩大芯片可用空间!

主要说一下这点。
我相信每个芯片或多或少在存储的时候都会因为芯片的配置导致代码不能完全利用空间。

举个例子:
32kB的空间芯片,flash地址从0到0x7FFF,一般在下载程序的时候,编译器会预编译,把不同的代码分配到不同的区,比如向量表一般从0开始,代码区在后面。这样便有一个现象是向量表和代码区之间存在空隙,这样就浪费掉了一部分空间。
image.png

如上图,有一块区域为全FF,这块区域并没有装载任何代码。白白浪费了。
这块区域是怎么产生的呢?这就不得不提利用IAR编译时的一个文件:.icf文件。
下面是.icf文件。

/*###ICF### Section handled by ICF editor, don't touch! ****/
/*-Editor annotation file-*/
/* IcfEditorFile="$TOOLKIT_DIR$\config\ide\IcfEditor\cortex_v1_0.xml" */
/*-Specials-*/
define symbol __ICFEDIT_intvec_start__ = 0x00000000;
/*-Memory Regions-*/   //  ###定义ROM区块和RAM 区块
define symbol __ICFEDIT_region_ROM_start__   = 0x00000000;
define symbol __ICFEDIT_region_ROM_end__     = 0x00007FFF;
define symbol __ICFEDIT_region_RAM_start__   = 0x10000000;
define symbol __ICFEDIT_region_RAM_end__     = 0x10001FFF;
/*-Sizes-*/          // 定义堆栈
define symbol __ICFEDIT_size_cstack__   = 0x400;
define symbol __ICFEDIT_size_heap__     = 0x800;
/**** End of ICF editor section. ###ICF###*/
// 定义CRP区块
define symbol __CRP_start__   = 0x000002FC;
define symbol __CRP_end__     = 0x000002FF;


define memory mem with size = 4G;
define region ROM_region   = mem:[from __ICFEDIT_region_ROM_start__   to __ICFEDIT_region_ROM_end__] -  mem:[from  __CRP_start__ to __CRP_end__];
define region RAM_region   = mem:[from __ICFEDIT_region_RAM_start__   to __ICFEDIT_region_RAM_end__];
define region CRP_region   = mem:[from  __CRP_start__ to __CRP_end__];

define block CSTACK    with alignment = 8, size = __ICFEDIT_size_cstack__   { };
define block HEAP      with alignment = 8, size = __ICFEDIT_size_heap__     { };

initialize by copy { readwrite };
do not initialize  { section .noinit };

place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };
place in ROM_region   { readonly };
place in RAM_region   { readwrite,
                        block CSTACK, block HEAP };
place in CRP_region   { section .crp };

代码中除了定义了常见的ROM区块和RAM区之外,还有一部分向量表和堆栈区。

我们仔细观察会发现有一块用于定义CRP区块如下:

// 定义CRP区块
define symbol __CRP_start__   = 0x000002FC;
define symbol __CRP_end__     = 0x000002FF;

而结合全FF的区域可以看到,全FF区域结束的地址刚好是CRP区块定义的结束地址,也就是
define symbol CRP_end = 0x000002FF;
看到这,一个自然的想法便是如果CRP不用的话,可以把他取消,这样不就有了更多的空间了么?
但是在不明白CRP为何物的情况下,建议不要贸然更改,否则可能会引起不必要的错误。

查看芯片手册。
image.png

可以看到原来是代码保护功能。但是CRP只是占用了从0X2FC到0X2FF的四个字节长度,为什么0X2FC之前也会有这么多空间空闲着呢?

这就是编译器编译的问题了。

原来IAR编译器在对代码编译完成后,会根据.icf文件中的相应区块设置分配代码。
从零地址开始为向量表地址。之后为CRP地址,再之后为我们的代码区块地址。
这样因为我们定义的CRP起始地址为0X2FC,而向量表代码量又太少不会占满0X2FC之前的所有空间,所以这块空间便被空闲下来了。
我们的目的是把着跨区域充分利用起来,那么这个问题有没有办法解决呢?

答案是肯定的!

有人说我们可以把CRP的起始地址定义的靠前点不就行了。但是,查看芯片手册你会发现,这个地址是芯片的默认地址,芯片物理机制只认这个地址,因此你更改了之后,如果在0x2FC~0X2FF之间写入了其他数,有可能会对代码造成不可避免的灾难。

因此我们想到在代码中利用常量将这块区域占住。如下:

#pragma  location = 0x000002FC
__root const uint32_t  crp= 0xFFFFFFFF;// @".MYTEST";

这样的话从0X2FC~0X2FF这块其余被我们填充了FFFFFFFF,编译器便不会往这块区域分配代码了。
但是仅仅做这点改动,会发现代码区仍然如此,并没有改变分配,只是仅仅把CRP区块填充了FFFF而已。
在回过头来观察.icf文件,会发现在编译的时候IAR会根据自己设置的ROM区大小分配地址。如下:

define symbol __ICFEDIT_intvec_start__ = 0x00000000;
/*-Memory Regions-*/   //  ###定义ROM区块和RAM 区块
define symbol __ICFEDIT_region_ROM_start__   = 0x00000000;
define symbol __ICFEDIT_region_ROM_end__     = 0x00007FFF;

向量表的起始地址被定义为
intvec_start__ = 0x00000000;
ROM区被定义为0x0000~0x7FFF。由于我的代码达不到7FFF这个地址,这样编译在编译的时候会认为空间足够大,会优先从0x300也就是CRP后面开始放代码。这样会造成CRP前面直到向量表都是空闲的。

此时我们只需要把ROM空间分配的小一点,迫使编译器寻找CRP之前的空闲区域分配代码即可。

define symbol __ICFEDIT_intvec_start__ = 0x00000000;
/*-Memory Regions-*/   //  ###定义ROM区块和RAM 区块
define symbol __ICFEDIT_region_ROM_start__   = 0x00000000;
define symbol __ICFEDIT_region_ROM_end__     = 0x00000C00;

因为我的代码在编译完之后大约不到3kB的大小。因此我设置ROM结束地址为0xC00,这样编译器会寻找0xC00之前所有的空闲区域放置代码。
下图为我测试的结果。
image.png

可以看到相比之前,空闲FF的区域少了很多。而且代码都分配在了0xC00之前。
image.png

利用这种方法可以有效的释放未使用的空间,做到ROM空间最大化。
从而扩大芯片可用空间!!!

最后一点需要注意的。设置完.icf文件后,需要将.icf文件设置为每次编译均覆盖。具体如下。

image.png

这里写图片描述
这样便可以完美实现空间再利用。

本文作者原创,请勿转载

相关文章
|
7月前
|
存储 机器学习/深度学习 算法
内存学习(六):引导内存分配器(初始化)
内存学习(六):引导内存分配器(初始化)
124 0
|
7月前
|
编译器
LabVIEW使用数据引用减少内存
LabVIEW使用数据引用减少内存
71 2
|
7月前
|
Linux Shell API
LabVIEW最大内存块属性不存在
LabVIEW最大内存块属性不存在
37 2
|
C++
C和C++动态内存分配及内存注意事项(重要)
C和C++动态内存分配及内存注意事项(重要)
67 0
|
存储 安全 API
4.9 x64dbg 内存处理与差异对比
LyScript 插件中针对内存读写函数的封装功能并不多,只提供了最基本的`内存读取`和`内存写入`系列函数的封装,本章将继续对API接口进行封装,实现一些在软件逆向分析中非常实用的功能,例如ShellCode代码写出与置入,内存交换,内存区域对比,磁盘与内存镜像比较,内存特征码检索等功能,学会使用这些功能对于后续漏洞分析以及病毒分析都可以起到事半功倍的效果,读者应重点关注这些函数的使用方式。
79 2
4.9 x64dbg 内存处理与差异对比
模拟实现库函数memcpy--复制内存块。详细理解内存重叠及精准复制问题
模拟实现库函数memcpy--复制内存块。详细理解内存重叠及精准复制问题
154 0
|
存储 缓存 索引
【操作系统】第四章:非连续内存分配(Part2:页表)
【操作系统】第四章:非连续内存分配(Part2:页表)
324 0
|
iOS开发 异构计算
如何增加 iOS APP 虚拟地址空间及内存上限?XNU 内核源码解读
1. 引言 最近一段时间在做钉钉 iOS 内存专项治理,解决内存不足时的 jetsam 事件及 malloc 的异常崩溃。在进程创建时系统会为每个 app 设定内存最大使用上限,内核会维护一个内存阈值优先级列表,当设备内存不足时低优先级的 app 会首先被内核中止进程。在阅读 XNU 内核源码过程中我们发现提供系统了两种能力可以扩展 App 的虚拟地址空间(com.apple.developer.kernel.extended-virtual-addressing)和增加内存使用上限(com.apple.developer.kernel.increased-memory-limit)。
2306 0
如何增加 iOS APP 虚拟地址空间及内存上限?XNU 内核源码解读
|
存储 程序员 芯片
程序中节省几 kB 的内存有必要吗?
程序中节省几 kB 的内存有必要吗?
|
关系型数据库