为提高C程序中某部分的执行速度和效率,以及涉及到C中无法做到的机器语言操作时,使用汇编语言子例程是很明智的。汇编语言虽然编程、调试较繁,但其执行效率在某些情况下远远高于用C书写的子例程,如浮点数计算软件包。下面介绍C与汇编接口的基本技术以及C与汇编接口的实例。
1.参数的传递
C程序中的参数传递到汇编程序,是通过栈操作进行的。C源程序中的参数按其出现顺序的相反顺序被压入堆栈。如ADD-Num(x,y,z)子例程,参数Z先入栈,y其次,x最后入栈且位于栈顶。堆栈的地址变化是向下增长的,最后一个进入堆栈的参数总在内存的低端,它的地址=BP+偏移量。其中偏移量在小/紧凑模式下是4字节,在中/大/巨模式下是6字节。C传递到汇编的参数若是基本数据类型之一,则该参数实际值被拷贝到堆栈中(数组是传送地址),C语言函数执行时,将从堆栈中取出其参数的值。下标是各基本数据类型在栈中所占的字节数。
类型 栈中大小 类型 栈中大小
char 字 unsigned long 双字
unsigned char 字 float 双字
short 字 近程指针 字
unsigned short 字 远程指针 双字
int 字 数组 字(指向数组的指针)
long 双字 结构 几个字
2.值的返回
经汇编程序处理的结果,通过AX和DX寄存器返值给C主程序。一般情况下只需通过AX寄存器即可将汇编程序的返回值传递给C程序,否则还需使用DX寄存器。至于结构变量、浮点数、双精度数、则存放在一块静态存贮区内,在AX中返回指向它们的指针。下面是返回值与寄存器的对应关系:
C程序中数据类型 汇编语言返回值存储单元
整型/字符型/ NEAR指针 AX
长整型 高字节在DX中,低字节在AX中
远程指针 段值在DX,偏移量在AX
3.段与组
内存中64字节的一块区域叫做“段”,段不能从绝对贮存器空间的任意字节上开始,只能从叫做节的每16字节的边界上开始。处理器中包含四个段寄存器,每当处理器为某种目的而访问存贮器时,不论是取指令、读写数据还是把数据压入堆栈,总是使用四个段寄存器中的一个,其中CS用于读取指令,DS用于数据存取,ES用做扩展的DS,SS是堆栈所在地。
C语言允许用户创建集中不同的程序和数据信息。
类型 意义
代码 编译程序产生的全部可执行代码
全程数据 可供所有模块方位,可以初始化,也可以未初始化
静态数据 仅在一个特定模块或函数内可以访问的变量,可以初始化,也可以未初始化
参数 一个函数的输入变量
局部变量 函数执行时存在,函数退出时消失
C编译程序将这些不同类型的变量保存在几个不同的段中。如下表:
段名 内容
-BSS 未初始化的静态数据(除在源文件中显示说明为far或huge之外)
-DATA 用于存放所有未初始化的全局数据和初始化了的全局和静态数据
FAR-DATA 初始化了的显示说明为far的全局或静态数据
FAR-BSS 未初始化的显示说明far的全局或静态数据
STACK 局部变量
CONST 只读常数
-TEXT 代码段
其中,-DATA,-CONST,-BSS和STACK段被组合为叫做DGROUP的组。在正常执行中,数据段和栈段寄存器均指向这一组的开始。另外,一个叫做NULL的段被作为包含在这一组中的第一个段,该段中含有编译程序版权说明,用于检测指针是否有非法使用的情况。在程序开始和结束时都要检查它。如果内容改变了,则出现严重错误。最有可能是程序错误地向一个空指针写入数据,它指向此段的开始,此时出现“空指针赋值”的错误信息。下图是段和组在内存中的分析:
堆栈
STACK DGROUP 从下到上为高内存区
-BSS
CONST
-DATA
NULL
FAR-DATA
FAR-BSS
-TEXT
标准模式下的段、组、类别表如下:
标准模式 段名 对齐类别 结合型 类型名 值
小/中 name-TEXT WORD PUBLIC ‘CODE’ -
-DATA WORD PUBLIC ‘DATA’ DGROUP
CONST WORD PUBLIC ‘CONST’ DGROUP
-BSS WORD PUBLIC ‘BSS’ DGROUP
STACK PARA STACK ‘STACK’ DGROUP
紧凑/大/巨 name-TEXT WORD PUBLIC ‘CODE’ -
FAR-DATA PARA PRIVATE ‘FAR-DATA’ -
FAR-BSS PARA PRIVATE ‘FAR-BSS’ -
-DATA WORD PUBLIC ‘DATA’ DGROUP
CONST WORD PUBLIC ‘CONST’ DGROUP
-BSS WORD PUBLIC ‘BSS’ DGROUP
STACK PARA STACK ‘STACK’ DGROUP
4.C调用汇编的一般格式
正文段描述
段模式
组描述
进栈
分配局部数据存储区(可省)
保存寄存器值
程序主体
送返回值到C
恢复寄存器值
退栈
正文段结束
正文描述一般按如下给出:
subname(可省) -TEXT SEGMENT BYTE PUBLIC ‘CODE’
subname(可省) -TEXT ENDS
段描述如下给出:
-DATA SEGMENT WORD PUBLIC ‘DATA’
-DATA ENDS
CONST SEGEMENT WORD PUBLIC ‘CONST’
CONST ENDS
-BSS SEGEMENT WORD PUBLIC ‘BSS’
-BSS ENDS
组描述如下给出:
DGROUP GROUP -DATA,LONST,-BSS
进栈为: push bp
move bp,sp
分配局部数据存储区可根据实际例子的需要而设置,不一定是必须的。通常利用堆栈来保留局部数据空间。如需要保留m个字节的局部数据空间,可使用指令“Sub Sp,m”。
为方便起见,m最好为偶数,如保留8个字节的局部数据存储区为Sub Sp,8,可利用bp的负位移来访问这些局部数据区,bp-2,bp-4,bp-6和bp-8。
保留寄存器的值主要是保留在子程序体重被破坏掉的寄存器,如SI,DI和DS,只需在主子程序体之前加上Push寄存器名指令即可。
主子程序体当然用途不同编写就不同,下一节给出几个示例。
送返回值是自动的,唯一需做的是要把返回的值放在适合该值返回的寄存器中,如要返回一个整数,只需将其存入AX寄存器。
恢复寄存器值需要将在主子程序体前保留的那些寄存器值弹出,若保留了局部数据空间,可以使用指令mov sp,bp来恢复。
5. 注意事项
a.在汇编子程序中供C程序使用的变量名和过程名前必须添加下划线,以与C程序的命名约定一致。
b.在汇编子程序中供C程序使用的变量,必须用public
说明:public-变量名;与此呼应。在C程序中也必须用
Extern对汇编变量进行说明:extern int-变量名
c.汇编子程序中变量的定义必须与C主程序中变量的说明一致,其关系如下表:
数据类型(C语言) 数据类型(汇编语言) 数据长度(字节)
char DB BYTE 1
int short DW WORD 2
long DD DWORD 4
float double DQ QWORD 8
d.在供C程序调用的汇编子程序中,必须用public伪操作指明该过程(子程序)是可以供外部模块使用的,如“public-过程名”。
与此相应,在C程序中应该说明汇编子程序是外部引用函数。
例如“extern void 过程名(void)”。
当外部引用函数无返值(void)或是int型时,对它的说明可以缺省。
e.Turbo C对大小写字母是很敏感的,因此必须使C在程序中汇编模块调用语句的函数名与汇编语言中过程名大、小写一致,否则将不能正确连接。但如果用Turbo C/TurboC++/Borland C++进行编译链接之前,将继承开发环境的Options/Linker中选择大小写敏感开关的Case-sensitive link置成关闭状态,再进行编译链接时,将不会区分大小写字母。