打印
[经验分享]

C51 内存优化

[复制链接]
4807|93
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
biechedan|  楼主 | 2024-7-20 14:10 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
浅谈 C51 内存优化
    对 51 单片机内存的认识,很多人有误解,最常见的是以下两种

① 超过变量128后必须使用compact模式编译
   实际的情况是只要内存占用量不超过 256.0 就可以用 small 模式编译
② 128以上的某些地址为特殊寄存器使用,不能给程序用
   与 PC 机不同,51 单片机不使用线性编址,特殊寄存器与 RAM 使用重复的重复的地址。但访问时采用不同的指令,所以并不会占用 RAM 空间。

    由于内存比较小,一般要进行内存优化,尽量提高内存的使用效率。

    以 Keil C 编译器为例,small 模式下未指存储类型的变量默认为data型,即直接寻址,只能访问低 128 个字节,但这 128 个字节也不是全为我们的程序所用,寄存器 R0-R7必须映射到低RAM,要占去 8 个字节,如果使用寄存组切换,占用的更多。

    所以可以使用 data 区最大为 120 字节,超出 120 个字节则必须用 idata 显式的指定为间接寻址,另外堆栈至少要占用一个字节,所以极限情况下可以定义的变量可占 247 个字节。当然,实际应用中堆栈为一个字节肯定是不够用的,但如果嵌套调用层数不深,有十几个字节也够有了。


为了验上面的观点,写了个例子

#define LEN 120
data UCHAR tt1[LEN];
idata UCHAR tt2[127];

void main()
{
    UCHAR i,j;

    for(i = 0;  i < LEN; ++i )
    {
        j = i;
        tt1[j] = 0x55;
    }
}

可以计算 R0-7(8) + tt1(120) + tt2(127) + SP(1) 总共 256 个字节

keil 编译的结果如下:
Program Size: data=256.0 xdata=0 code=30
creating hex file from "./Debug/Test"...
"./Debug/Test" - 0 Error(s), 0 Warning(s).
(测试环境为 XP + Keil C 7.5)

    这段代码已经达到了内存分配的极限,再定义任何全局变量或将数组加大,编译都会报错 107

    这里要引出一个问题:为什么变量 i、j 不计算在内?
    这是因为 i、j 是局部变量,编译器会试着将其优化到寄存器 Rx 或栈。问题也就在这了,如果局部变量过多或定义了局部数组,编译器无法将其优化,就必须使用 RAM 空间,虽然全局变量的分配经过精心计算没有超出使用范围,仍会产生内存溢出的错误!

    而编译器是否能成功的优化变量是根据代码来的
    上面的代码中,循环是臃肿的,变量 j 完全不必要,那么将代码改成

UCHAR i;
UCHAR j;

for(i = 0;  i < LEN; ++i )
{
    tt1[i] = 0x55;
}

再编译看看,出错了吧!
因为编译器不知道该如何使用 j,所以没能优化,j 须占 RAM 空间,RAM 就溢出了。
(智能一点的编译器会自动将这个无用的变量去掉,但这个不在讨论之列了)

另外,对 idata 的定义的变量最好放在 data 变量之后

对于这一种定义


uchar c1;
idata uchar c2;
uchar c3;
变量 c2 肯定会以间接寻址,但它有可能落在 data 区域,就浪费了一个可直接寻址的空间


变量优化一般要注意几点:

    ①让尽可能多的变量使用直接寻址,提高速度
      假如有两个单字节的变量,一个长119的字符型数组
      因为总长超过 120 字节,不可能都定义在 data 区
      按这条原则,定义的方式如下:


      data UCHAR tab[119];
      data UCAHR c1;
      idata UCHaR c2;
      但也不是绝的,如果 c1, c2 需要以极高的频率访问,而 tab 访问不那么频繁
      则应该让访问量大的变量使用直接寻址:

      data UCAHR c1;
      data UCHaR c2;
      idata UCHAR tab[119];
      这个是要根据具体项目需求来确定的

    ②提高内存的重复利用率
      就是尽可能的利用局部变量,局部变量还有个好处是访问速度比较快
      由前面的例子可以看出,局部变量 i, j 是没有单独占用内存的
      子程序中使用内存数目不大的变量尽量定义为局部变量

    ③对于指针数组的定义,尽可能指明存储类型
       尽量使用无符号类型变量

      一般指针需要一个字节额外的字节指明存储类型
     8051 系列本身不支持符号数,需要外加库来处理符号数,一是大大降低程序运行效率,二是需要额外的内存


    ④避免出现内存空洞

      可以通过查看编译器输出符号表文件(.M51)查看
      对前面的代码,M51文件中关于内存一节如下:


* * * * * * *   D A T A   M E M O R Y   * * * * * * *
REG     0000H     0008H     ABSOLUTE     "REG BANK 0"
DATA    0008H     0078H     UNIT         ?DT?TEST
IDATA   0080H     007FH     UNIT         ?ID?TEST
IDATA   00FFH     0001H     UNIT         ?STACK


第一行显示寄存器组0从地址0000H开始,占用0008H个字节
第二行显示DATA区变量从0008H开始,占用0078H个字节
第三行显示IDATA区变量从0080H开始,占用007F个字节
第四行显示堆栈从00FFH开始,占0001H个字节

由于前面代码中变量定义比较简单,且连续用完了所有空间,所以这里显示比较简单
变量定义较多时,这里会有很多行

如果全局变量与局部变量分配不合理,就有可能出现类似下面的行

0010H     0012H                  *** GAP ***
      该行表示从0010H开始连续0012H个字节未充分利用或根本未用到

使用特权

评论回复
沙发
guijial511| | 2024-7-24 08:27 | 只看该作者
51单片机内核是不是都一样的

使用特权

评论回复
板凳
uiint| | 2024-8-4 09:33 | 只看该作者
频繁的函数调用会增加堆栈的使用。可以考虑将多个小函数合并为一个函数,或者使用内联函数(inline)来减少函数调用的开销。

使用特权

评论回复
地板
iyoum| | 2024-8-4 11:32 | 只看该作者
尽量减少全局变量的数量,因为它们会一直占据内存,直到程序结束。
使用局部变量,并考虑使用静态局部变量来保持状态。

使用特权

评论回复
5
mickit| | 2024-8-4 12:42 | 只看该作者
当使用指针时,应明确指定指针指向的内存类型和指针本身的存储类型,以减少内存占用。

使用特权

评论回复
6
jtracy3| | 2024-8-4 13:28 | 只看该作者
定期进行代码重构,去除无用代码,合并相似功能,以减少代码体积和内存占用。

使用特权

评论回复
7
tabmone| | 2024-8-4 14:32 | 只看该作者
中断服务程序应尽可能简短,避免使用过多的堆栈空间。

使用特权

评论回复
8
gouguoccc| | 2024-8-4 21:48 | 只看该作者
用C51单片机的应用本身就不会很复杂

使用特权

评论回复
9
youtome| | 2024-8-5 12:06 | 只看该作者
尽量避免使用动态内存分配(如malloc和free),因为这会增加内存管理的复杂性,并可能导致内存碎片。

使用特权

评论回复
10
alvpeg| | 2024-8-5 15:03 | 只看该作者
循环展开:适当展开循环可以减少循环控制结构所需的指令数量。
消除冗余代码:使用编译器提供的优化选项,如Keil uVision中的优化等级。

使用特权

评论回复
11
bartonalfred| | 2024-8-5 16:57 | 只看该作者
使用编译器的优化选项,如优化代码生成、去除未使用的代码和数据等,以提高程序的运行效率和内存利用率。

使用特权

评论回复
12
cemaj| | 2024-8-5 21:52 | 只看该作者
使用编译器选项来查看堆栈使用情况,并尝试减少堆栈空间的需求。

使用特权

评论回复
13
elsaflower| | 2024-8-6 12:38 | 只看该作者
全局变量占用整个程序的生命周期,因此应尽量减少全局变量的数量。可以考虑将全局变量替换为局部变量或通过函数参数传递数据。

使用特权

评论回复
14
macpherson| | 2024-8-6 14:12 | 只看该作者
当需要存储多个布尔值时,可以使用位域来节省空间。位域允许在一个字节或字中存储多个标志位。

使用特权

评论回复
15
uiint| | 2024-8-6 14:33 | 只看该作者
合理使用存储类型
data:直接访问片内低 128 字节的 RAM,速度快但空间有限。
idata:间接访问片内 256 字节的 RAM。
xdata:访问片外扩展的 RAM,速度较慢。
根据变量的使用频率和大小,合理选择存储类型。例如,频繁使用的小变量可放在 data 区,较大或不常使用的变量放在 xdata 区。

使用特权

评论回复
16
febgxu| | 2024-8-6 15:04 | 只看该作者
在必要时,可以实现代码和数据的共享,例如使用查表方法替代复杂的运算,从而节省内存空间。

使用特权

评论回复
17
hudi008| | 2024-8-6 17:00 | 只看该作者
尽量使用字符数组而不是字符串指针,以减少内存开销。

使用特权

评论回复
18
plsbackup| | 2024-8-6 19:57 | 只看该作者
尽量减少全局变量的使用,因为它们会占用宝贵的内存空间。对于需要跨函数使用的变量,可以考虑使用静态变量。

使用特权

评论回复
19
maqianqu| | 2024-8-6 20:46 | 只看该作者
在多任务或多线程环境中,合理地共享资源和数据,以减少内存的使用。

使用特权

评论回复
20
sesefadou| | 2024-8-8 09:21 | 只看该作者
通过查看编译器输出的符号表文件(.M51文件),检查是否存在未使用的内存空洞,并相应调整变量的位置和大小,使内存连续且紧凑。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

294

主题

7936

帖子

12

粉丝