对于一个大的文件,为了便于管理,一个好的办法时把一个大文件分为若干个小文件,每个小文件包含一部分相关的功能,这样功能将显得很整洁,而且移植到其它工程的时候也很方便,把文件copy过去即可。
对于汇编,我们也许知道,可以使用orgxxxx来指定函数的地址(org是一个段内指定偏移的伪指令),但是当指定这个地址时,是否与其它函数冲突呢?有可能其它函数过长已经占用了这个地址。难道要数手指计算函数的长度吗?另一个问题是主函数怎样调用被调函数呢?
在回答这些问题之前,先来看看使用的开发工具是怎样工作的。首先A51汇编器汇编文件后会生成.obj文件,这并不是最终需要的文件。通常还要使用一个叫链接器(如BL51)的工具,把多个obj文件组合起来,生成最终的二进制代码文件(或其它格式的文件)。虽然传递给链接器的参数是一个或多个obj文件,但是链接器在链接时却是按段来计算的。每个obj文件里包含的都是一些绝对地址段或可重定位段。链接器必须为可重定位段重新计算及分配地址,并计算绝对地址段是否重叠。因此:可重定位段可以保证多目标文件链接时不发生目标地址重叠,故绝对地址段只适用用某些特殊场合,如固定I/O口或中断向量的入口地址。绝对地址段就是这个段的地址在传入链接器的时候已经固定,链接器不能改变这个段的地址。可重定位段就是这个段的地址需要链接器分配,如是代码段的段内的跳转等指令的偏移也需要重新进算。
段的另一种分法可分为代码段、数据段。数据段又分为以初始化数据段和未初始化数据段。这里不细说。
通过上面可知,如果一个函数是一个段,而这个段是可重定位的,那么就可以不用考虑代码的地址问题了,把这个问题留给了链接器。剩下来的问题就是怎样把一个函数变为一个段的问题,(当然一个段可包括很多函数,在汇编中,函数只是一个说法而已)。大部分的汇编器都会提供伪指令来定义绝对段及可重定位段(写法上有可能有区别而已)。
在A51中,定义段的伪指令是
cseg -- 绝对代码段
bseg -- 绝对位段
dseg -- 绝对内部直接寻址(data)数据段
iseg -- 绝对内部间接寻址(idata)数据段
xseg -- 绝对外部(xdata)数据段
segment -- 定义一个可重定位段
rseg -- 选择一个可重定位段
使用说明:
NAME 模块名 //用来指明当前程序模块 重定位段名 SEGMENT 段类型 [重定位类型] //重定位类型为可选,
段类型为可用的段类型有CODE、DATA、XDATA、IDATA、BIT。重定位类型为PAGE、INPAGE、INBLOCK、BITADDRESSABLE、UNIT、OVERLAYABLE。段名等价于段符号,在表达式中用段名表示复合段中的基地址。
每个函数都包含在一个段内。org 0 改为了 cseg at 0, org 50h则被删除,这样链接程序会自动的为每个重定位段分配地址,也就相当于为函数分配的地址,这样函数与函数之间就没有了gas(^_^,这是Keil的术语,也就相当于空隙),当有gas时,程序是没有使用的,白白浪费了。对于51这样的系统可重定位段的另一个好处就是空间覆盖处理,这将在下一节细说。
也许读者会有这样的疑问,之前的示例并没有定义任何的段,它们工作得也很好啊,那汇编器及链接器是怎么工作的呢?链接器的工作仍然是不变的,只是汇编器在扫描文件时发现文件没有定义段,会为每个空间分配一个绝对段,地址从0开始。以code空间为例,如果没有定义段,则默认段从0开始,这时文件中的第一条指令的地址就是0开始,而org伪指令是指定段内偏移的,这里的org 0有没有也就无所谓了。不信读者可以把这条语句去掉再汇编一次。
而当工程里开始,这时链接器就会提示地址空间重叠。因此可重定位段的用处就在于此:它不会造成地址空间重叠。
下面将介绍的几个伪指令对于多文档工作就特别的有用了。
extrn -- 指示符号是外部的,并告诉模块是什么类型
extern -- 跟extrn差不多,相当于extrn的一种特例
public -- 指示符号可在其它模块使用
使用方法如下:
EXTRN class (symbol [ , symbol ... ])
EXTRN class:type (symbol [ , symbol ... ])
EXTERN class:type (symbol [ , symbol ... ])
PUBLIC symbol [ , symbol ... ]
中括号内是可选部分。
C51编译器采用了一个扩展关键字reentrant作为定义函数时的选项,需要将一个函数定义为可重入函数时,只要在函数后面加上关键字reentrant即可。
与非可重入函数的参数传递和局部变量的存储分配方法不同,C51编译器为可重入函数生成一个模拟栈(相对于系统堆栈或是硬件堆栈来说),通过这个模拟栈来完成参数传递和存放局部变量。模拟栈以全局变量?C_IBP、?C_PBP和?C_XBP作为栈指针(系统堆栈栈顶指针为SP),这些变量定义在DATA地址空间,并且可在文件startup.a51中进行初始化。根据编译时采用的存储器模式,模拟栈区可位于内部(IDATA)或外部(PDATA或XDATA)存储器中。如表1所示:
存储模式 |
栈指针 |
栈区域 |
Small |
?C_IBP(1 字节) |
间接访问的内部数据存储器(IDATA),栈区最大为256字节 |
Compact |
?C_PBP(1字节) |
分页寻址的外部数据存储器(PDATA),栈区最大为256字节 |
Large |
?C_XBP(2字节) |
外部数据存储器(XDATA),栈区最大为64K |
表1注意:51系列单片机的系统堆栈(也叫硬件堆栈或常规栈)总是位于内部数据存储器中(SP为 8位寄存器,只能指向内部),而且是“向上生长”型的(从低地址向高地址),而模拟栈是“向下生长”型的。