谈谈Keil 中C51的内存分配与优化

发布者:BlossomWhisper最新更新时间:2017-01-19 来源: eefocus关键字:Keil  C51  内存分配 手机看文章 扫描二维码
随时随地手机看文章

看到这篇C51的内存分配和优化的文章,个人觉得分析的十分到位,在这里转给大家

 

C51的内存分配不同于一般的PC,内存空间有限,采用覆盖和共享技术。在Keil编译器中,经过编译后,会形成一个M51文件,在其内部可以详细的看到内存的分配情况。

C51内存常见的两个误区:

(1)       变量超过128字节后必须用COMPACT模式。

其实,只要不超过256字节,都可以用SMALL模式

(2)       内部RAM,128字节以上的是SFR用,不给程序用。

其实,由于C51寻址的不同,高128字节也可以用来存储变量,虽与SFR地址相同,但寻址的方式不同。

下面通过几个程序来看内存的分配。

*******************************************************************************

//程序1:

#include

void main()

{}

Program Size: data=9.0 xdata=0 code=16

TYPE    BASE      LENGTH    RELOCATION   SEGMENT NAME

            -----------------------------------------------------

 

            * * * * * * *   D A T A   M E M O R Y   * * * * * * *

            REG     0000H     0008H     ABSOLUTE     "REG BANK 0"

            IDATA   0008H     0001H     UNIT         ?STACK

 

*******************************************************************************

从上面可以看到,即使程序内部无任何变量和函数data也会为9.0。这9个字节内存分别为R0-R8和一个堆栈指针(C51的堆栈是“grow up”,即使堆栈中没有内容,也会有一个栈底指针)。data区中由于R0-R8占有8个存储空间,因此data区最大为120字节(栈在所有的变量空间 之后),如果超过120个字节则由idata显式的指定为间接寻址。对于整个内部256字节的RAM,在极端的情况下,最大的变量为247字节。

 

当定义全局变量时

*******************************************************************************

//程序2:

#include

#define uint unsigned int

#define uchar unsigned char

uchar a;

uint b;

void main()

{}

Program Size: data=12.0 xdata=0 code=16

TYPE    BASE      LENGTH    RELOCATION   SEGMENT NAME

            -----------------------------------------------------

 

            * * * * * * *   D A T A   M E M O R Y   * * * * * * *

            REG     0000H     0008H     ABSOLUTE     "REG BANK 0"

            DATA    0008H     0003H     UNIT         ?DT?MAIN

            IDATA   000BH     0001H     UNIT         ?STACK

 

            * * * * * * *   C O D E   M E M O R Y   * * * * * * *

            CODE    0000H     0003H     ABSOLUTE   

            CODE    0003H     000CH     UNIT         ?C_C51STARTUP

            CODE    000FH     0001H     UNIT         ?PR?MAIN?MAIN

*******************************************************************************

存在全局变量时,根据全局变量的类型分配相应的存储空间。

看下面的程序

*****************************************************************

//程序3:

#include

#define uint unsigned int

#define uchar unsigned char

uchar a;

uint b;

 

uint sum(uint c)

{

    uint d;

    d = c;

    return d;

}

void main()

{}

Program Size: data=12.0 xdata=0 code=17

TYPE    BASE      LENGTH    RELOCATION   SEGMENT NAME

            -----------------------------------------------------

 

            * * * * * * *   D A T A   M E M O R Y   * * * * * * *

            REG     0000H     0008H     ABSOLUTE     "REG BANK 0"

            DATA    0008H     0003H     UNIT         ?DT?MAIN

            IDATA   000BH     0001H     UNIT         ?STACK

 

            * * * * * * *   C O D E   M E M O R Y   * * * * * * *

            CODE    0000H     0003H     ABSOLUTE   

            CODE    0003H     000CH     UNIT         ?C_C51STARTUP

            CODE    000FH     000CH     UNIT         ?PR?MAIN?MAIN

            CODE    001BH     0001H     UNIT         ?PR?_SUM?MAIN

********************************************************************

与上面的程序想比较,发现内存并没有任何的变化。

看下面的程序

***************************************************************************

//程序4:

#include

#define uint unsigned int

#define uchar unsigned char

uchar a;

uint b;

 

uint sum(uint c)

{

    uint d;

    d = c;

    return d;

}

void main()

{

    b = sum(5);

}

Program Size: data=12.0 xdata=0 code=28

TYPE    BASE      LENGTH    RELOCATION   SEGMENT NAME

            -----------------------------------------------------

 

            * * * * * * *   D A T A   M E M O R Y   * * * * * * *

            REG     0000H     0008H     ABSOLUTE     "REG BANK 0"

            DATA    0008H     0003H     UNIT         ?DT?MAIN

            IDATA   000BH     0001H     UNIT         ?STACK

 

            * * * * * * *   C O D E   M E M O R Y   * * * * * * *

            CODE    0000H     0003H     ABSOLUTE   

            CODE    0003H     000CH     UNIT         ?C_C51STARTUP

            CODE    000FH     000CH     UNIT         ?PR?MAIN?MAIN

            CODE    001BH     0001H     UNIT         ?PR?_SUM?MAIN

****************************************************************************

这与上面的内存使用相同,在这个程序中通过反汇编,查看编译后的汇编程序可以发现,参数的传递通过通用寄存器完成,没有占用新的内存。编译器将其优 化的通用寄存器(寄存器一般传递3个参数,超过3个参数时,多余的参数通过分配空间地址的方式来访问。但是分配的内存空间包含了寄存器传递的3个参数在内 的所有参数的空间。详见《Parameter And Local Variable 》和《Parameter and Register》)和栈中(程序7),但是如果参数或局部变量过多,则情况就完全不同(程序6)。

再看下面的程序

*******************************************************************

//程序5

#include

#define uint unsigned int

#define uchar unsigned char

uchar a;

uint b;

 

uint sum(uint c)

{

    uint d;

}

void main()

{

}

Program Size: data=16.0 xdata=0 code=21

TYPE    BASE      LENGTH    RELOCATION   SEGMENT NAME

            -----------------------------------------------------

 

            * * * * * * *   D A T A   M E M O R Y   * * * * * * *

            REG     0000H     0008H     ABSOLUTE     "REG BANK 0"

            DATA    0008H     0004H     UNIT         ?DT?_SUM?MAIN

            DATA    000CH     0003H     UNIT         ?DT?MAIN

            IDATA   000FH     0001H     UNIT         ?STACK

 

            * * * * * * *   C O D E   M E M O R Y   * * * * * * *

            CODE    0000H     0003H     ABSOLUTE   

            CODE    0003H     000CH     UNIT         ?C_C51STARTUP

            CODE    000FH     0005H     UNIT         ?PR?_SUM?MAIN

            CODE    0014H     0001H     UNIT         ?PR?MAIN?MAIN

******************************************************************

同样的程序,内部RAM的使用情况又发生了变化。函数未被调用,且参数与局部变量都没有使用。

源文件经过编译后形成obj文件,各个obj文件就是一个模块,每个模块中都含有代码段和数据段,也就是,代码在ROM占有多少CODE空间,数据在RAM里占用多少空间等信息。

obj(lib)文件然后经过l51.exe(bl51.exe),就是说把可执行代码模块根据连接定位参数地址上连接在一起();数据段也连接在 一起,在ram空间中分配.对ram空间的分配中就有一个连接过程"覆盖分析".调用一个c函数,就会为这个函数所使用的ram空间进行分配(一些局部变 量),这个函数返回时再回收分配给他的ram空间,根据函数互相之间的调用前后关系,编译器就可以时实的知道ram空间的使用情况(其中就存在一个函数重 入的问题),作为连接时ram空间分配的参数. 如果源文件中的函数(模块)从来没有被任何函数显示的调用(所谓非显示调用就是这段代码,连接器目前还不知道这段代码什么时候会被调用或是否会被调用), 连接时就会为它分配永远有效的ram空间(就象全局变量),不会被回收。


*******************************************************************

//程序6

#include

#define uint unsigned int

#define uchar unsigned char

uchar a;

uint b;

 

uint sum2(uint e,uint f,uint g,uint j)

{

    uint i;

    i = e+f+g+j;

    return i;

}

void main()

{

    b = sum2(1,2,3,4);

}

Program Size: data=20.0 xdata=0 code=58

TYPE    BASE      LENGTH    RELOCATION   SEGMENT NAME

            -----------------------------------------------------

            * * * * * * *   D A T A   M E M O R Y   * * * * * * *

            REG     0000H     0008H     ABSOLUTE     "REG BANK 0"

            DATA    0008H     0008H     UNIT         _DATA_GROUP_

            DATA    0010H     0003H     UNIT         ?DT?MAIN

            IDATA   0013H     0001H     UNIT         ?STACK

**************************************************************

在上面的程序中如果将sum2中的j去掉,那么data = 12.0。

通过程序5和6可以看到,两个程序的M51文件中的蓝色部分的segement name的名称有很大的区别。_DATA_GROUP_是一个OVERALY GROUPS(覆盖组)。它是链接器产生的可覆盖的一个数据段。而上面的程序5中的?DT?_SUM?MAIN,则是一个函数段。

在Keil中对OVERALY GROUPS的解释为:

When performing overlay analysis, the linker creates groups of segments that are overlaid. The group name indicates the memory type of the variables that it includes.

Group Name

Segment

Prefix

Memory

Model

Description

_BIT_GROUP_

?BI?

All

Variables and arguments of type bit.

_DATA_GROUP_

?DT?

SMALL

Variables and arguments other than bit.

_PDATA_GROUP_

?PD?

COMPACT

Variables and arguments other than bit.

_XDATA_GROUP_

?XD?

LARGE

Variables and arguments other than bit.

When the linker overlays function data memory and creates a group, that groups appears in the linker map file's memory map section.

START     STOP      LENGTH    ALIGN  RELOC    MEMORY CLASS   SEGMENT NAME

======================================================================

* * * * * * * * * * *   D A T A   M E M O R Y   * * * * * * * * * * * * *

000000H   000007H   000008H   ---    AT..     DATA           "REG BANK 0"

000008H   00001AH   000013H   BYTE   UNIT     DATA           _DATA_GROUP_

00001BH   00001BH   000001H   BYTE   UNIT     IDATA          ?STACK

Groups are created based on the memory model of the function (which specifies the memory class where parameters and variables are stored) and on any variables defined with a specific memory space.

在keil生成的M51函数中有OVERLAY MAP OF MODULE,对程序中函数调用的覆盖有详细的说明。

//程序7

#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;

    }

}

变量i和j通过编译器的优化占用了通用寄存器,R0-8[8]+tt1[120]+tt2[127]+SP[1]共256字节;

而如果将程序7中j=i删掉。上面声明了uchar j,但是下面没有应用,因此编译器不知道该如何处理j,就让其占用了一个RAM空间不再存在通用寄存器中,出现了内存溢出。(有的编译器会将不用的变量自动删除)

局部变量占用通用寄存器,变量在声明时就被分配了空间;局部变量只有在被声明、赋值且被使用后才认为是局部变量被放置在通用寄存器中,否则被认为是全局变量;在循环中,R7中放置循环数。(自加)

由于data区的存取速度快,变量尽量的放在该区,但是由于idata可以访问整个256字节的RAM,如果data区变量较少,idata型的变量也会占有data区,减少了可直接访问的存储空间,因此在变量定义的时候尽量将idata型的变量定义在data型变量后。

uchar c1;

idata uchar c2;

uchar c3;

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

变量的优化


(1)       将访问频率高的变量放在data区

(2)       提高内存的复用率。尽可能的利用局部变量,由于局部变量的存取速度快。在程序7中可以看到,i和j没有占用内存,子程序中使用内存数量不大的变量尽量定义为局部变量。

(3)       对于指针数组的定义,尽量指明存储类型。尽量使用无符号类型变量。8051不支持有符号数计算,需要通过其他的库来处理,大大降低了运行的速度,增加了内存的利用。

(4)       避免出现内存空洞。在M51中可以很清楚的看到内存的分配情况,如果全局变量和局部变量的分配不合理,有时会出现下面的情况:

0010H     0012H                  *** GAP ***

表示从0010开始有0012H个字节未利用。造成这种情况的原因是局变量太多、多个子程序中的局部变量数目差异太大、使用了寄存器切换但未充分利用

(5)避免出现未使用的变量或者函数。


关键字:Keil  C51  内存分配 引用地址:谈谈Keil 中C51的内存分配与优化

上一篇:keil51下使用sprintf问题
下一篇:KEIL C51 printf格式化输出特殊用法

推荐阅读最新更新时间:2024-03-16 15:31

简单实现 汇编及C语言混合 keil9.0工程源码
系统设计核心意图:使用定时器,在延时过程中运行其它的任务。 工程源码:链接: https://pan.baidu.com/s/1LEV9qYmUn6SdemGz7TH6dw 提取码: iua5 切换任务并记录位置,保证在时间到后能切换回来。(在任务中切换出去,在定时器中切换回来。)(时间片轮转) 可以支持同时8个任务。 工程就两个文件:汇编操作系统rtos_c.asm 及 C语言示例 Test.c Test.c /* 开发工具:keil c51 V9.0 及注册机 芯片:标准C8051 晶振:12M 源码任务数:加主任务共4个 2019-05-07 */ //----------------------
[单片机]
简单实现 汇编及C语言混合 <font color='red'>keil</font>9.0工程源码
C51 INT0中断程序
//INT0中断程序 #include reg52.h void delay(unsigned int x); main() { IT0=1; //下降沿触发 EX0=1; //开INT0中断 EA=1; P1=0xff; while(1) { } } void int0() interrupt 0 { IT0=0; //关中断, P1=~P1; delay(20000); //延时 IT0=1; //开中断 } void delay(unsigned int x) //延时 { unsigned int i; for(i=0;i x;i++) {} }
[单片机]
如何使用Keil5开发MSP430及Tiva系列开发板
如何使用keil5开发msp430,Tiva系列 Launchpad系列是Ti德州仪器推出的一系列低成本开发评估套件,即使放到现在,这系列的MCU仍旧不失为一款优异的微控制器芯片。Ti Launchpad平台的另一个系列板卡——Tiva C系列的TM4C123G Launchpad评估套件EK-TM4C123GXL,基于高性能的ARM Cortex-M4处理器。 由于TI官方提供的CCS用起来很麻烦,安装也很麻烦。故我们想办法在MDK平台上开发对吧。同样大家也可以用IAR for msp430 首先需要安装MDK5即Keil5.未安装的同学请转此 https://baijiahao.baidu.com/s?id=1596425
[单片机]
如何使用<font color='red'>Keil</font>5开发MSP430及Tiva系列开发板
深入C51中断向量表
在FLASH中有一组特殊的保留单元000H~00ABH 0000H-0002H 这三个单元包含一个无条件跳转指令(LJMP),当系统复位后,PC=0000H,跳转到main函数(不确切) LJMP 是三字节指令。就是由 指令码02H(一个字节) + 16位地址(2个字节) 组成 16位地址就是Main()函数的地址,其实也不然,由于main()函数在调用之前,要进行堆栈的初始化之类的工作,在KEIL中,这部分代码是自动生成的(对于C程序来说),跳转地址实际就是这个堆栈汇编的代码开头处! 以下是一个C程序转换成汇编的部分代码 C:0x0000 020410 LJMP C:0410
[单片机]
深入<font color='red'>C51</font>中断向量表
C51编程19-中断篇(串行通讯3)
MCS-51单片机提供了4种串口的方式,但是我们只有方式1最常用(可变的10位串行通讯方式),下面就方式1的使用进行讲解。 在开始之前先明确一个概念,中断会产生中断标志位,而CPU检测到中断标志位后,如果没有其他更高的中断在执行,CPU会响应该中断,并进入中断服务函数。串行通讯属于中断方式的一种,它服从这个概念。此外串行通讯并不是需要进入串行通讯的时候,会产生中断标志位;而是发送或者接收成功后,会产生中断标志位。 通过中断向量,可以知道串行通讯只有一个中断号4,也就是说当进入无论是发送或者接收成功,都会触发中断服务函数,因此如果需要区分发送与接收需要在中断服务函数中实现区分,可以通过判断中断标志位(TI == 1或者RI
[单片机]
AVR,C51和PIC八位单片机性能比较
八位单片机由于内部构造简单,体积小,成本低廉,在一些较简单的控制器中应用很广。即便到了本世纪,在单片机应用中,仍占有相当的份额。由于八位单片机种类繁多,本文仅将常用的几种在性能上作一个简单的比较,供读者在使用时作参考。 1. 51系列 应用最广泛的八位单片机首推Intel的51系列,由于产品硬件结构合理,指令系统规范,加之生产历史“悠久”,有先入为主的优势。世界有许多著名的芯片公司都购买了51芯片的核心专利技术,并在其基础上进行性能上的扩充,使得芯片得到进一步的完善,形成了一个庞大的体系,直到现在仍在不断翻新,把单片机世界炒得沸沸扬扬。有人推测,51芯片可能最终形成事实上的标准MCU芯片。 51系列优点之一是它从内部的硬件到
[单片机]
如何采用C51单片机读写CAT24C32
#include #include//我的自定义LCD1602头文件 /*--------------------------------------------------------------- 24C32可存储4K(1024*4)个字节(8bit),因此寻址地址最大为0x0FFF,24C32为从机。 ----------------------------------------------------------------*/ #defineWriteDeviceAddress0x0A2//定义器件在I2C总线中的写地址(注意:根据自定义从机地址接口改变) #defineReadDviceAdd
[单片机]
如何采用<font color='red'>C51</font>单片机读写CAT24C32
Keil uCos 2.52 stm32 【worldsing笔记】
1、uCOSii V2.52 a、加了7个可以配置的钩子函数宏 #define OS_TASK_CREATE_HOOK_EN 0 /* 任务创建时调用钩子函数 使能 1 / 禁止 0 */ #define OS_TASK_STAT_HOOK_EN 0 /* 统计任务运行时调用钩子函数 使能 1 / 禁止 0 */ #define OS_TASK_SW_HOOK_EN 1 /* 任务调度时调用钩子函数 使能 1 / 禁止 0 */ #define OS_TIME_TICK_HOOK_EN 1 /* 滴答中断时调用钩子函数 使
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved