汇编语言之程序的基本结构.docx

上传人:小飞机 文档编号:3613259 上传时间:2023-03-14 格式:DOCX 页数:38 大小:54.67KB
返回 下载 相关 举报
汇编语言之程序的基本结构.docx_第1页
第1页 / 共38页
汇编语言之程序的基本结构.docx_第2页
第2页 / 共38页
汇编语言之程序的基本结构.docx_第3页
第3页 / 共38页
汇编语言之程序的基本结构.docx_第4页
第4页 / 共38页
汇编语言之程序的基本结构.docx_第5页
第5页 / 共38页
亲,该文档总共38页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

《汇编语言之程序的基本结构.docx》由会员分享,可在线阅读,更多相关《汇编语言之程序的基本结构.docx(38页珍藏版)》请在三一办公上搜索。

1、汇编语言之程序的基本结构第6章 程序的基本结构 在前面几章,我们分别介绍了用汇编语言进行程序设计所需要的几个最基本的知识:内存单元的寻址方式,变量定义和各种汇编指令格式。在掌握了这些基本内容之后,就需要学习如何把它们组成一个完整的汇编语言程序。 6.1 源程序的基本组成 汇编语言源程序的组成部分有:模块、段、子程序和宏等。一个模块对应一个目标文件,当开发较大型的应用程序时,该程序可能由若干个目标文件或库结合而成的。有关模块和子程序的知识和宏在第7章介绍,有关宏的知识将在第9章中叙述。 6.1.1 段的定义 微机系统的内存是分段管理的,为了与之相对应,汇编语言源程序也分若干个段来构成。8086C

2、PU有四个段寄存器,在该系统环境下运行的程序在某个时刻最多可访问四个段,而80386及其以后的CPU都含有六个段寄存器,于是,在这些系统环境下开发的运行程序在某个时刻最多可访问六个段。 不论程序在某个时刻最多能访问多少个段,在编程序时,程序员都可以定义比该段数更多的段。在通常情况下,一个段的长度不能超过64K,在80386及其以后系统的保护方式下,段基地址是32位,段的最大长度可达4G。 段的长度是指该段所占的字节数: 、如果段是数据段,则其长度是其所有变量所占字节数的总和; 、如果段是代码段,则其长度是其所有指令所占字节数的总和。 在定义段时,每个段都有一个段名。在取段名时,要取一个具有一定

3、含义的段名。 段定义的一般格式如下: 段名 SEGMENT 对齐类型 组合类型 类别 ;段内的具体内容 段名 ENDS 其中:“段名”必须是一个合法的标识符,前后二个段名要相同。可选项“对齐类型”、“组合类型”和“类别”的说明作用请见6.3节中的叙述。 一个数据段的定义例子: DATA1 SEGMENT word1 DW 1, 9078H, ? byte1 DB 21, World DD 12345678H DATA1 ENDS 一个代码段的例子: CODE1 SEGMENT MOV AX, DATA1 ;把数据段DATA1的段值送AX MOV DS, AX ;把AX的值送给DS,即:DS存储

4、数据段的段值 MOV AX, 4C00H INT 21H ;调用DOS功能,结束程序的运行 CODE1 ENDS 6.1.2 段寄存器的说明语句 在汇编语言源程序中可以定义多个段,每个段都要与一个段寄存器建立一种对应关系。建立这种对应关系的说明语句格式如下: ASSUME 段寄存器名:段名, 段寄存器名:段名, 其中:段寄存器是CS、DS、ES、SS、FS和GS,段名是在段定义语句说明时的段名。 在一条ASSUME语句中可建立多组段寄存器与段之间的关系,每种对应关系要用逗号分隔。例如, ASSUME CS:CODE1, DS:DATA1 上面的语句说明了:CS对应于代码段CODE1,DS对应于

5、数据段DATA1。 在ASSUME语句中,还可以用关键字NOTHING来说明某个段寄存器不与任何段相对应。下面语句说明了段寄存器ES不与某段相对应。 ASSUME ES:NOTHING 在通常情况下,代码段的第一条语句就是用ASSUME语句来说明段寄存器与段之间的对应关系。在代码段的其它位置,还可以用另一个ASSUME语句来改变前面ASSUME语句所说明的对应关系,这样,代码段中的指令就用最近的ASSUME语句所建立的对应关系来确定指令中的有关信息。 例6.1 汇编语言段及其段说明语句的作用。 DATA1 SEGMENT word1 DW 5678h byte1 DB ABCDEFG DATA

6、1 ENDS DATA2 SEGMENT word2 DW 1234h word3 DW 9876h DATA2 ENDS DATA3 SEGMENT byte2 DB ? DATA3 ENDS CODE1 SEGMENT CS:CODE1, DS:DATA1, ASSUME ES:DATA2 MOV AX, DATA1 MOV DS, AX MOV AX, DATA2 MOV ES, AX MOV AX, word1 MOV word2, AX ASSUME DS:DATA3, ES:NOTHING MOV AX, DATA3 MOV DS, AX MOV BL, byte2 ;定义数据段D

7、ATA1 ;定义数据段DATA2 ;定义数据段DATA3 ;编写代码段CODE1 ;(1) ;(2) ;(3) ;(4) ;(5) ;访问段DATA1中的字变量word1 ;访问段DATA2中的字变量word2 ;(6) ;访问段DATA3中的字节变量byte2 ;(7) ;(8) MOV AX, 4C00H INT 21H CODE1 ENDS 语句(1)和(6)分别说明了段和段寄存器之间的对应关系,其中语句(6)重新说明语句(1)所指定的对应关系。 二组语句(2)和(3)、(4)和(5)实现对段寄存器DS和ES赋初值。ASSUME说明语句只起说明作用,它不会对段寄存器赋值,所以,必须对有关

8、段寄存器赋值。在以后的其它源程序中也都是用此方法来实现对数据段寄存器赋值的。 语句(7)和(8)是调用中断21H的4CH号功能来结束本程序的执行,程序的返回代码由寄存器AL来确定。结束本程序执行的指令是所有主模块必须书写的语句。 注意:代码段寄存器不能由程序员在源程序中对其赋值,其值是由操作系统在装入它进入系统运行时自动赋值的。 6.1.3 堆栈段的说明 堆栈段是一个特殊的段,在程序中可以定义它,也可以不定义。除了要生成COM型执行文件的源程序外,一个完整的源程序一般最好定义堆栈段。如果在程序中不定义堆栈段,那么,操作系统在装入该执行程序时将自动为其指定一个64K字节的堆栈段。 在程序没有定义

9、堆栈段的情况下,在由连接程序生成执行文件时,将会产生一条如下的警告信息,但程序员可以不理会它,所生成的执行文件是可以正常运行的。 warning xxxx: no stack segment (其中:xxxx是错误号) 在源程序中,可用以下方法来定义堆栈段。 方法1: STACK1 SEGMENT TOP LABEL WORD STACK1 ENDS DB 256 DUP(?) ;256是堆栈的长度,可根据需要进行改变 以上堆栈段的定义如图6.1所示。由于堆栈是按地址从大到小的存储单元顺序来存放内容的,所以,在堆栈存储单元说明语句之后,再说明一个栈顶别名,这样,对栈顶寄存器SP的赋值就很方便。

10、 在源程序的代码段中,还要添加如下程序段,才能把段STACK1当作堆栈段来使用。 ASSUME SS:STACK1 ;可在代码段的段指定语句中一起说明 CLI ;禁止响应可屏蔽中断 MOV AX, STACK1 MOV SS, AX MOV SP, offset TOP ;给堆栈段的栈顶寄存器SP赋初值 STI ;恢复响应可屏蔽中断 方法2: STACK1 SEGMENT STACK ;定义一个堆栈段,其段名为STACK1 DB 256 DUP(?) STACK1 ENDS 图6.1 堆栈段定义示意图 上述段定义说明了该段是堆栈段,系统会自动把段寄存器SS和栈顶寄存器SP与该堆栈段之间建立相应

11、的关系,并设置其初值,而不用在代码段对它们进行赋值。 除了以上二种方法外,还有一种更简洁的方法来定义堆栈段,有关内容请见第6.4.2节中的叙述。 6.1.4 源程序的结构 下面的程序是一个完整的源程序,其功能是在屏幕上显示字符串“Hello, World.”。读者可参考此结构编写自己的简单程序。 例6.2 在屏幕上显示字符串“HELLO,WORLD.” 解:可运行下面的控件,用鼠标左键单击程序中的某一行,可阅读其含义;单击“内存”可切换内存内容的显示方式。 伪指令END表示源程序到此为止,汇编程序对该语句之后的任何内容都不作处理,所以,通常情况下,伪指令END是源程序的最后一条语句。 伪指令E

12、ND后面可附带一个在程序中已定义的标号,由该标号指明程序的启动位置。 如果源程序是一个独立的程序或主模块,那么,伪指令END后面一定要附带一个标号;如果源程序仅是一个普通模块,那么,其END后面就一定不能附带标号。 6.2 程序的基本结构 在学习高级语言程序设计时,我们知道了程序的三大主要结构:顺序结构、分支结构和循环结构。在汇编语言的源程序也同样有此三大结构,所不同的是它们的表现形式不同。用高级语言编写程序时,由于不使用“转移语句”而使这三种结构清晰明了。 但在汇编语言的源程序中,很难不使用“转移语句”(除非是一些只有简单功能的程序),有时甚至会有各种各样的“转移语句”。由于存在这些转移语句

13、,就使得:汇编语言源程序的基本结构显得不太明确。如果源程序的编写者思维混乱,编写出来的源程序在结构上就会显得杂乱无章,反之,如果编写者条理清晰,安排的操作井然有序,那么,编写出来的程序在结构上就会一目了然。 总之,不论是高级语言的源程序,还是汇编语言的源程序,其程序的三大基本结构也还是万变不离其宗的。 6.2.1 顺序结构 顺序结构是最简单的程序结构,程序的执行顺序就是指令的编写顺序,所以,安排指令的先后次序就显得至关重要。另外,在编程序时,还要妥善保存已得到的处理结果,为后面的进一步处理直接提供前面的处理结果,从而避免不必要的重复操作。 例6.3 编写程序段,完成下面公式的计算(其中:变量X

14、和Y是32位有符号数,变量A,B和Z是16位有符号数)。 A(X-Y+24)/Z的商,B(X-Y+24)/Z的余数 解: DATA1 SEGMENT X DD ? Y DD ? Z DW ? A DW ? B DW ? DATA1 ENDS CODE1 SEGMENT MOV AX, X MOV DX, X+2 SUB AX,Y SBB DX, Y+2 ADD AX, 24D ADC DX, 0 IDIV Z MOV A, AX MOV B, DX CODE1 ENDS ;用(DX:AX)来保存32位变量X的数值 ;(DX:AX)-(Y+2:Y) ;(DX:AX)+24 在编程序时,常常需要交

15、换二变量之值。假设需要交换值的变量名为:var1和var2,临时增加的变量名为temp。常用的算法如下: temp = var1 var1 = var2 var2 = temp 例6.4 假设有二个字变量word1和word2,编写程序段实现交换其值的功能。 解: 方法1:用汇编语言指令简单“直译”上面的 交换数据方法 DATA1 SEGMENT word1 DW ? word2 DW ? temp DW ? DATA1 ENDS CODE1 SEGMENT MOV AX, word1 MOV temp, AX ;上二语句实现语句“temp=word1” MOV AX, word2 MOV w

16、ord1, AX ;上二语句实现语句“word1=word2” MOV AX, temp MOV word2, AX ;上二语句实现语句“word2=temp” CODE1 ENDS 这种方法虽然也能完成功能,但显然其不能充分利用汇编语言的特点,程序效率很低。 方法2:用汇编语言指令的特点来直接编译 DATA1 SEGMENT word1 DW ? word2 DW ? DATA1 ENDS CODE1 SEGMENT MOV AX, word1 XCHG AX, word2 MOV word1, AX ;能XCHG word1, word2来代替这三条指令吗? CODE1 ENDS 该方法充

17、分利用了汇编语言的特点,不仅省去了中间变量temp的定义,而且程序的效率也提高了。 6.2.2 分支结构 分支结构是一种非常重要的程序结构,也是实现程序功能选择所必要的程序结构。由于汇编语言需要书写转移指令来实现分支结构,而转移指令肯定会破坏程序的结构,所以,编写清晰的分支结构是掌握该结构的重点,也是用汇编语言编程的基本功。 在程序中,当需要进行逻辑分支时,可用每次分二支的方法来达到程序最终分多支的要求,也可是用地址表的方法来达到分多支的目的。 一、显示转移指令实现的分支结构 在高级语句中,分支结构一般用IF语句来实现,在汇编语言中,课用无条件转移指令或条件转移指令实现的分支结构。如图6.2给

18、出了二种常用的分支结构。 在编写分支程序时,要尽可能避免编写“头重脚轻”的结构,即:当前分支条件成立时,将执行一系列指令,而条件不成立时,所执行的指令很少。这样就使后一个分支离分支点较远,有时甚至会遗忘编写后一分支程序。这种分支方式不仅不利于程序的阅读,而且也不便将来的维护。 所以,在编写分支结构时,一般先处理简单的分支,再处理较复杂的分支。对多分支的情况,也可遵循“由易到难”的原则。因为简单的分支只需要较少的指令就能处理完,一旦处理完这种情况后,在后面的编程过程中就可集中考虑如何处理复杂的分支。 (a) if endif结构 (b) ifelseendif结构 图6.2 分支结构的二种结构

19、例6.5 已知字节变量CHAR1,编写一程序段,把它由小写字母变成大写字母。 解: DATA1 SEGMENT CHAR1 DB ? DATA1 ENDS CODE1 SEGMENT MOV AL, CHAR1 CMP AL, a JB next CMP AL, z JA next SUB CHAR1, 20H ;指令AND CHAR1, 0DFH也可以 next: CODE1 ENDS 例6.6 编写一程序段,计算下列函数值。其中:变量X和Y是有符号字变量。 解: DATA1 SEGMENT X DW ? Y DW ? DATA1 ENDS CODE1 SEGMENT MOV AX, X C

20、MP AX, 0 JGE case23 ADD AX, 10 JMP result case23: CMP AX, 10D JG case3 MOV BX, 30D IMUL BX ;第一种情况的计算结果 ;第二种情况的计算结果 JMP result case3: SUB AX, 9 result: MOV Y, AX CODE1 ENDS ;第三种情况的计算结果 ;把计算结果保存到变量Y中 例6.7 把下列C语言的语句改写成等价的汇编语言程序段(不考虑运算过程中的溢出)。 If (a+b 0 & c%2 = 0) a = 62; else a = 21; 其中:变量a,b和c都是有符号的整型

21、(int)变量。 解: DATA1 SEGMENT A DW ? B DW ? C DW ? DATA1 ENDS CODE1 SEGMENT MOV AX, A ADD AX, B JLE _ELSE TEST C, 1 JNZ _ELSE MOV A, 62D JMP NEXT _ELSE: MOV A, 21D NEXT: CODE1 ENDS ;ADD指令会改变算术标志位 ;C%2=0,也就是:看C的最低位是否为0 例6.8 用地址转移表实现下列C语言的switch语句,其中:变量A和B是有符号的整型(int)变量。 switch (a%8) case b = 32; 0: break

22、; case 1: case b = a + 43; 2: break; case b = 2*a; 解: DATA1 SEGMENT A DW ? B DW ? DW case0. case12, case12, Table case3 3: break; case b-; 4: break; case 5: case 6: DATA1 case printf(“Function CODE1 7: 5_6_7”); break; DW case4, case567, case567, case567 MSG DB Function 5_6_7$ ENDS SEGMENT MOV AX, A

23、MOV BX, AX ;得到BX的低三位,实现AND BX, 7 a%8的计算 ;由于地址表是字类型,SHL BX, 1 其下标要乘2 ;利用地址表实现多路转JMP TableBX 移 case0: MOV B, 32D JMP next case12: ADD AX, 43D MOV B, AX JMP next case3: SHL AX, 1 MOV B, AX JMP next case4: DEC B JMP next case567: LEA DX, MSG MOV AH, 9 INT 21H JMP next next: CODE1 ENDS 用地址表实现多路转移的关键在于:转移

24、入口的地址表和转移情况可整数化。如果这二个要求有一个不满足,或很难构造,则无法使用该方法。 为了改善汇编语言源程序的结构,减少显式转移语句所带来混乱,在宏汇编MASM 6.11系统中,增加了表达分支结构的伪指令。该伪指令的书写格式与高级语言的书写方式相类似,汇编程序在汇编时会自动增加转移指令和相应的标号。理解并掌握该知识,对将来学习编译原理课程也有一定的帮助。 分支伪指令的具体格式如下: 格式1: .IF condition ;以英文“句号”开头 指令序列 ;条件condition成立时所执行的指令序列 .ENDIF 格式2: .IF condition 指令序列1 .ELSE 指令序列2 ;

25、条件condition不成立时所执行的指令序列 .ENDIF 格式3: .IF condition1 指令序列1 .ELSEIF condition2 指令序列2 ;条件condition2成立时所执行的指令序列 .ENDIF 其中:条件表达式“condition”的书写方式与C语言中条件表达式的书写方式相似,也可用括号来组成复杂的条件表达式。 条件表达式中可用的操作符有:=(等于)、!=(不等)、(大于)、=(大于等于)、(小于)、=a & ;语句象C语言语句吗? AL=z SUB CHAR1, 20H .ENDIF 也可把例6.6的代码段部分就可写成如下程序段: MOV AX, X .IF

26、 AX 0 ADD AX, 10 .ELSEIF AX Z ;显示整个大写字母表 MOV AH, 09H MOV DX, OFFSET MSG3 INT 21H .WHILE 1 ;循环条件为永真的循环 MOV AH, 07H INT 21H ;不带回显地从键盘读一个字符 .BREAK .IF AL = 13 ;如果输入“回车”键,则终止循环 .CONTINUE .IF (AL9) 环 MOV DL, AL MOV AH, 02H INT 21H ;显示所输入的数字字母 .ENDW MOV AX, 4C00H INT 21H CODE1 ENDS END START 6.3 段的基本属性 在通

27、常情况下,一个复杂的应用程序会由若干个模块组成,一个模块又会含有多个段。而不同模块的段之间、同一模块的段之间往往存在某种联系,这种联系就要体现在段属性的说明上。 段定义的一般格式如下: 段名 SEGMENT 对齐类型 组合类型 类别 段名 ENDS 段属性“对齐类型”、“组合类型”和“类别”要按此顺序说明,但这些可选项可根据需要选择书写。如果源程序中不指定某个属性,那么,汇编程序将使用该属性的缺省值。 程序中的段名可以是唯一的,也可以与其它段同名。在同一模块中,如果有二个段同名,则后者被认为是前段的后续,这样,它们就属同一段。 当同一模块出现二个同名段时,则后者的可选项属性要么与前者相同,要么

28、不写其属性而选用前者的段属性。 例6.13 同段名的作用 DATA1 SEGMENT ;第一个数据段 MSG DB Hello, DATA1 ENDS CODE1 SEGMENT ;第一个代码段 ASSUME CS:CODE1, DS:DATA1 START: MOV AX, DATA1 MOV DS, AX MOV DX, offset MSG MOV AH, 9 INT 21H CODE1 ENDS DATA1 SEGMENT ;第二个数据段 DB World.$ DATA1 ENDS CODE1 SEGMENT ;第二个代码段 MOV AX, 4C00H INT 21H CODE1 EN

29、DS END START END 在上面的例子中,第二个数据段是第一个数据段的后续,汇编程序把它们是合二为一,上述的代码段也如此。 下面,详细说明段属性的含义及其作用。 6.3.1 对齐类型(ALIGN) 对齐类型表示当前段对起始地址的要求,连接程序(LINK.EXE)按表6.1的地址格式来定位段的起始地址。在进行段定位时,会根据其定位类型进行定位的,所以,各段之间就有可能出现一些空闲字节,即可能浪费几个字节单元。 段对齐类型PARA是一个适用于所有段类型的对齐类型,它也是缺省的对齐类型。对齐类型BYTE和WORD通常用于数据段的定位,对齐类型DWORD通常用于80386及其以后CPU代码段的

30、定位。 表6.1 段对齐类型与段起始地址之间的对应关系 对齐类最多的空闲起始地址(二进制) 功能说明 型 字节数 xxxx xxxx xxxx xxxx 下一个字BYTE 0 xxxx 节地址 xxxx xxxx xxxx xxxx 下一个字WORD 1 xxx0 地址 xxxx xxxx xxxx xxxx 下一个双DWORD 3 xx00 字地址 xxxx xxxx xxxx xxxx 下一个节PARA 15 0000 地址 xxxx xxxx xxxx 0000 下一个页PAGE 127 0000 地址 6.3.2 组合类型(COMBINE) 组合类型是告诉连接程序如何把不同模块中段名相

31、同的段合并在一起。具体的组合类型如下: NONE PUBLIC 表示当前段在逻辑上独立于其它模块,并有其自己的基地址。NONE是缺省的组合类型。 表示当前段与其它模块中同段名的PUBLIC类型段组合成一个段。组合的先后次序取决于LINK程序中目标模块排列的次序。在组合时,后续段的起始地址要按其对齐类型进行定位,所以,同名段之间可能有间隔。 表示当前段与其它模块中同名段重叠,也就是说,它们的起始地址相同。最终段的长度是同名段的最大长度。由于段覆盖,所以,前一同名段中的初始化数据被后续段的初始数据覆盖掉。 组合类型STACK表示当前段是堆栈栈,其组合情况与PUBLIC相同。 COMMON STAC

32、K AT 数值表该数值表达式是当前段所指定的绝对起始地址的段地址。 达式 6.3.3 类别(CLASS) 类别是一个由程序员指定的用单引号括起来的字符串。如果一个段没有给出类别,那么,这个段的类别就为空。类别是用于段的分类,连接程序利用该类别来调整同名、同类别的段,并使它们相邻。典型的类别是Data和Code。如果指定某段的类别是Code,那么,该段最好是代码段,这样,有的调试程序(如:CodeView)就可以顺序工作。 例如: DATA1 SEGMENT WORD PUBLIC Data DATA1 ENDS 上述段定义说明了该段的起始地址是下一个字地址、组合类型为PUBLIC、段类别是Da

33、ta。 6.3.4 段组(GROUP) 段组伪指令GROUP是用于把源程序模块中若干个段结合成一个组,并对该段组定义一个段组名。段组伪指令的格式如下: 段组名 GROUP 段名, 段名, 其中:段名之间要用逗号间隔,段名也可以用表达式“SEG 变量”或“SEG 标号”。 下面举例说明段组伪指令的使用方法和作用。 例6.12 段组的作用 方法1:用一个段寄存器对应二个数据段 DATA1 SEGMENT ;第一个数据段 b1 DB 10h DATA1 ENDS DATA2 SEGMENT ;第二个数据段 b2 DB 23h DATA2 ENDS CODE1 SEGMENT ASSUME CS:CO

34、DE1, ;(1) DS:DATA1 START: MOV AX, DATA1 ;(2)把数据段DATA1的段值赋给段寄存器MOV DS, AX DS MOV BL, b1 ;(3)引用DS来访问DATA1中的变量b1 ASSUME DS:DATA2 ;(4) MOV AX, DATA2 ;(5)把数据段DATA2的段值赋给段寄存器MOV DS, AX DS MOV AL, b2 ;(6)引用DS来访问DATA2中的变量b2 CODE1 ENDS END START 在上例中,语句(1)说明DS与DATA1建立联系,语句(2)对DS赋值,语句(3)用DS来访问DATA1段的变量名。语句(4)说明DS与DATA2建立联系,语句(5)对DS赋值,语句(6)用DS来访问DATA2段的变量名。 在该例子中,因为只使用一个段寄存器DS来对应二个数据段,所以,需要切换DS的对应关系(如:语句(4)。但我们也可以用段寄存

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号