700 likes | 835 Views
微机原理及接口技术. 主编 梁建武. 中国水利水电出版社. 3.2 汇编语言程序设计. 完整的汇编语言源程序由段组成 一个汇编语言源程序可以包含若干个代码段、数据段、附加段或堆栈段,段与段之间的顺序可随意排列 需独立运行的程序必须包含一个代码段,并指示程序执行的起始点,一个程序只有一个起始点 所有的可执行性语句必须位于某一个代码段内,说明性语句可根据需要位于任一段内 通常,程序还需要一个堆栈段. 3.2.1 汇编语言的语句格式.
E N D
微机原理及接口技术 主编梁建武 中国水利水电出版社
3.2 汇编语言程序设计 完整的汇编语言源程序由段组成 一个汇编语言源程序可以包含若干个代码段、数据段、附加段或堆栈段,段与段之间的顺序可随意排列 需独立运行的程序必须包含一个代码段,并指示程序执行的起始点,一个程序只有一个起始点 所有的可执行性语句必须位于某一个代码段内,说明性语句可根据需要位于任一段内 通常,程序还需要一个堆栈段
3.2.1 汇编语言的语句格式 ⑴执行性语句——执行性语句用于表达处理器指令(也称为硬指令),汇编后对应一条指令代码。由处理器指令组成的代码序列是程序设计的主体 标号: 硬指令助记符 操作数,操作数 ;注释 ⑵说明性语句——说明性语句用于表达伪指令,指示源程序如何汇编、变量怎样定义、过程怎么设置等 名字 伪指令助记符 参数,参数,…… ;注释
3.2.1 汇编语言的语句格式(续) • 硬指令(Instruction)——使CPU产生动作、并在程序执行时才处理的指令 硬指令就是第2章学习的处理器指令,与具体的处理器有关、与汇编程序无关 • 伪指令(Directive)——不产生CPU动作、在程序执行前由汇编程序处理的说明性指令 伪指令与具体的处理器类型无关,但与汇编程序有关。不同版本的汇编程序支持不同的伪指令
1.简化段定义格式 .model small ;定义程序的存储模式(小型模式) .stack ;定义堆栈段(默认是1KB空间) .data ;定义数据段 …… ;数据定义 .code ;定义代码段 start: mov ax,@data ;程序起始点 mov ds,ax ;设置DS指向用户定义的数据段 …… ;程序代码 mov ax,4c00h int 21h ;程序结束点,返回DOS …… ;子程序代码 end start ;汇编结束,指明程序起始点start
2.完整段定义格式 完整段定义利用SEGMENT和ENDS一对伪指令定义逻辑段 同时需要配合ASSUME伪指令指明逻辑段是代码段、堆栈段、数据段还是附加段 完整段定义的优势是可以指明逻辑段的定位、组合、类别等属性;而简化段定义只能采用系统默认的属性 完整段定义和简化段定义的实质是一致的
3.2.2 常量、变量和标号 汇编语言的数据可以简单分为常量和变量 常量可以作为硬指令的立即数或伪指令的参数,变量主要作为存储器操作数 汇编语言语句中的名字和标号具有逻辑地址和类型属性,主要用做地址操作数,也可以作为立即数和存储器操作数
1.常量 • 常量表示一个固定的数值,它又分成多种形式 • 常数 • 字符串 • 符合常量 • 数值表达式
2.变量 变量实质上是指内存单元的数据,虽然内存单元地址不变,但其中存放的数据可以改变 变量需要事先定义才能使用 变量定义(Define)伪指令为变量申请固定长度为单位的存储空间,并可以同时将相应的存储单元初始化 定义后的变量可以利用变量名等方法引用其中的数据,即变量的数值
2.变量(续) • 变量的定义 • 变量定义的汇编语言格式为: 变量名 伪指令 初值表 • 变量名为用户自定义标识符,表示初值表首元素的逻辑地址,常称为符号地址。变量名也可以没有 • 初值表是用逗号分隔的参数,主要由常量、数值表达式或“?”组成。其中“?”表示未赋初值 • 多个存储单元如果初值相同,可以用复制操作符DUP进行定义: 重复次数 DUP(重复参数) • 变量定义伪指令有DB、DW、DD等
2.变量(续) • DB伪指令用于分配一个或多个字节单元,并可以将它们初始化为指定值 • 初值表中每个数据一定是字节量,存放一个8位数据: • 可以是0~255的无符号数 • 或是-128~+127带符号数 • 也可以是字符串常数
2.变量(续) • DW伪指令用于分配一个或多个字单元,并可以将它们初始化为指定值 • 初值表中每个数据是字量,一个字单元可用于存放任何16位数据: • 一个段地址 • 一个偏移地址 • 两个字符 • 0~65535之间的无符号数 • -32768~+32767之间的带符号数
2.变量(续) • 例: • VAR_DATA SEGMENT • MAX EQU 64H ;定义符号常量,MAX = 64H • BDATA1 DB 01100001b, 97, 61H, ’a’ ;定义字节变量,存储单元均为61H • BDATA2 DB MAX, MAX + 10H ;定义字节变量,用常量和数值表达式来赋初值 • WDATA1 DW 1234H ;定义字变量 • DW ?, 4 dup(1122H) ;预留一个字空间,将1122H复制定义4个 • DDATA DD 12345678H ;定义双字变量 • CHAR DB ‘a’, ’s’, ’m’ ;定义字符变量,以字节形式存储 • STRING DB ‘COMPUTER’ ;定义字符串变量 • ARRAY DB 2 DUP(1,2,2 DUP(3)) ;复制相同的操作数,内容为:01 02 03 03 01 02 03 03 • VAR_DATA ENDS
3.2.3顺序程序设计 没有分支、循环等转移指令的程序,会按指令书写的前后顺利依次执行,这就是顺序程序 顺序结构是最基本的程序结构 完全采用顺序结构编写的程序并不多见 举例
3.2.3顺序程序设计 举例 • 例:通过查表,实现一位0~9十进制数转换为对应格雷码显示。 • 程序源代码: • DATA SEGMENT • TAB DB 18H,34H,05H,06H,09H,0AH,0CH,11H,12H,14H • NUM DB 05H, 08H • DATA ENDS • CODE SEGMENT • ASSUME CS: CODE, DS: DATA • START: MOV AX, DATA • MOV DS, AX • MOV BX, OFFSET TAB ;BX指向TAB表 • MOV AL, NUM ;AL取得一位十六进制数,该数是TAB表中的位移 • AND AL, 0FH ;低4位有效,高4位清零
3.2.3顺序程序设计 举例 • XLAT ;AL←DS:[BX + AL],为0AH • MOV DL, AL ;以下DOS功能调用,显示格雷码 • MOV AH, 2 • INT 21H • MOV AL, NUM + 1 • AND AL, 0FH • XLAT • MOV DL, AL • MOV AH, 2 • INT 21H • CODE ENDS • END START
3.2.4分支程序设计 • 分支程序根据条件是真或假决定执行与否 • 判断的条件是各种指令,如CMP、TEST等执行后形成的状态标志 • 转移指令Jcc和JMP可以实现分支控制 • 分支结构有 • 单分支结构 • 双分支结构 • 多分支结构
1.转移类指令 控制转移类指令用于实现分支、循环、过程等程序结构,是仅次于传送指令的常用指令 指令包括:JMP/Jcc/LOOP CALL/RET INT n/IRET LOOPZ/LOOPNZ
(1)无条件转移指令 • JMP label • ;程序转向label标号指定的地址 • 只要执行无条件转移指令JMP,就使程序转到指定的目标地址处,从目标地址处开始执行指令 • 操作数label是要转移到的目标地址(目的地址、转移地址) • JMP指令分成4种类型: ⑴ 段内转移、相对寻址 ⑵ 段内转移、间接寻址 ⑶ 段间转移、直接寻址 ⑷ 段间转移、间接寻址
(1)无条件转移指令(续) • 段内转移——近转移(near) • 在当前代码段64KB范围内转移( ±32KB范围) • 不需要更改CS段地址,只要改变IP偏移地址 • 段内转移——短转移(short) • 转移范围可以用一个字节表达,在段内-128~+127范围的转移 • 段间转移——远转移(far) • 从当前代码段跳转到另一个代码段,可以在1MB范围 • 更改CS段地址和IP偏移地址 • 目标地址必须用一个32位数表达,叫做32位远指针,它就是逻辑地址
(1)无条件转移指令(续) • 段内转移 • 段内直接短转移 • JMP SHORT Label ;IP←IP + 位移量(8位) • 段内直接转移 • JMP NEAR Label ;IP←IP + 位移量(16位) • 段内间接转移 • JMP reg16 / mem16 ;IP← (reg16) / (mem16) • 段间转移 • 段间直接转移 • JMP FAR Label ;CS← SEG Label, IP←OFFSET Label • 段间间接转移 • JMP mem32 ;CS←(mem32 + 2), IP←(mem32)
(2)条件转移指令 • Jcc label • ;条件满足,发生转移:IP←IP+8位位移量 • ;条件不满足,顺序执行 • 指定的条件cc如果成立,程序转移到由标号label指定的目标地址去执行指令;条件不成立,则程序将顺序执行下一条指令 • 操作数label是采用相对寻址方式的短转移标号 • 表示Jcc指令后的那条指令的偏移地址,到目标指令的偏移地址的地址位移 • 距当前IP地址-128~+127个单元的范围之内
2.分支程序设计 分支程序的结构可以有两种形式:双分支结构和多分支结构。
(1)简单双分支程序 简单的双分支程序是组成其他复杂程序的基本内容。遇到这一类问题首先要明确要判断的条件是什么;要使用哪个条件转移语句;条件满足的分支程序段要完成那些任务,条件不满足的分支程序要完成那些任务。 举例
(1)简单双分支程序 举例 • 例:从键盘输入一个字符,在其最高位加奇校验位后送入X单元中。键盘输入的是一个ASCII码,最高位为0.通过某种指令判断该字符中的1的个数的奇偶性,若为奇数个1,高位不变,否则,将高位置1。 • 程序段源代码: • DATA SEGMENT • X DB ? • DATA ENDS • CODE SEGMENT • ASSUME CS: CODE, DS: DATA • START: MOV AX, DATA • MOV DS, AX • MOV AH, 01H • INT 21H ;键盘输入一个字符 • AND AL, AL • JNP NEXT ;若ASCII码中有奇数个1,转NEXT • OR AL, 80H ;否则将AL中高位置1 • NEXT: MOV X, AL • MOV AH, 4CH • INT 21H ;程序结束 • CODE ENDS • END START
(2)多分支程序 • 多分支结构有若干个条件,每一个条件对应一个基本操作。多分支程序就是判断产生的条件,哪个条件满足,就转去执行该条件所对应操作的程序段。从若干分支程序中选择一个分支程序执行。 • 多分支结构实现的方法有: • 条件选择法 • 转移表法 • 地址表法 举例
(2)多分支程序 举例 • 例:从键盘输入一个字符串,将其中小写字母转换为大写字母,然后显示。该程序的执行,首先要判断输入字符是否为小写字母(ASCII码61H~7AH),如果是,则转换为大写字母(ASCII码41H~5AH)。 • 程序源代码: • DATA SEGMENT • KEYNUM = 255 • KEYBUF DB KEYNUM • DB 0 • DB KEYNUM DUP(0) • DATA ENDS • CODE SEGMENT • ASSUME CS:CODE,DS:DATA • START: MOV DX, OFFSET KEYBUF ;用DOS的0AH号功能调用 ;输入一个字符串 • MOV AH, 0AH • INT 21H ;回车符结束 • MOV DL, 0AH ;换行,用来在下一行显示转变换后的字符串
(2)多分支程序 举例 • MOV AH, 2 • INT 21H • MOV BX, OFFSET KEYBUF+1 ;取出字符串中的字符个数 • MOV CL, [BX] • MOV CH, 0 ;作为循环的次数 • AGAIN: INC BX • MOV DL, [BX] ;取出一个字符 • CMP DL, ‘a’ ;小于小写字母a,不需要处理 • JB DISP • CMP DL, ‘z’ ;大于小写字母z,不需要处理 • JA DISP • SUB DL, 20H ;小写字母,转换为大些字母 • DISP: MOV AH, 2 ;显示一个字符 • INT 21H • LOOP AGAIN ;循环处理,直到整个字符串处理完 • CODE ENDS • END START
3.循环程序设计 循环结构一般是根据某一条件判断为真或假来确定是否重复执行循环体 循环指令和转移指令可以实现循环控制;还可以采用MASM 6.x提供的循环控制伪指令实现 控制指令是用cx寄存器作为计数器,来控制程序的循环。它属于段内短转移类,即目的地址必须距本指令在-128B——+127B的范围内。该指令共有3条。 循环控制指令:LOOP、LOOPE/LOOPZ、LOOPNE/LOOPNZ
(1)LOOP指令 格式:LOOP LABEL 操作:(CX)←(CX)-1;若(CX)≠0,则转至LABEL处执行;否则退出循环,执行LOOP后面的指令。 等价于下面的指令: DEC CX JNZ LABEL
(2)LOOPE/LOOPZ指令 • 格式:LOOPE/LOOPZLABEL • 操作:(CX)←(CX)-1;若(CX)≠0,且ZF=1时,则转至LABEL处执行;否则退出循环,执行LOOPE/LOOPZ后面的指令。
(3)LOOPNE/LOOPNZ指令 • 格式:LOOPNE/LOOPNZLABEL • 操作:(CX)←(CX)-1;若(CX)≠0,且ZF=0时,则转至LABEL处执行;否则退出循环,执行LOOPNE/LOOPNZ后面的指令。
(1)循环控制指令 举例 • 例:若在存储器的数据段中有100个字节构成的数组,要求从该数组中找出“$”字符,然后将“$”字符前面的所有元素相加,结果保留在AL寄存器中。 • 程序段代码: • MOV CX, 100 • MOV SI, 00FFH • LABEL1: INC SI • CMP BYTE PTR[SI], ‘$’ • LOOPNE LABEL1 ;查找“$”字符 • SUB SI, 0100H • MOV CX, SI • MOV SI, 0100H • MOV AL, [SI] • DEC CX ;存放相加次数 • LABEL2: INC SI • ADD AL, [SI] • LOOP LABEL2 ;循环累加“$”字符前的所有字节 • HLT
(2)循环程序设计 循环程序的基本结构
(2)循环程序设计(续) • 循环结构的实现 • 1)计数循环 举例 • 2)条件循环 举例 • 3)多重循环 举例
(1)计算循环 • 计数循环通过测试并判断循环是否已进行预定的次数来控制循环。这种循环控制结构直观,便于程序设计,常用于循环次数已知的情况。每循环一次,计数器计数一次,直至计数器达到预定值,循环结束。
1)计数循环 • 例:找出从无符号字节数据存储变量VAR开始存放的N个数中的最大数放在BH中。 • 程序源代码: • DATA SEGMENT • VAR DB 5, 7, 19H, 23H, 0A0H • N EQU $ - VAR • DATA ENDS • CODE SEGMENT • ASSUME CS:CODE,DS:DATA • START: MOV AX, DATA • MOV DS, AX • MOV CX, N – 1 ;循环控制计数 • MOV SI, 0 • MOV BH, VAR[SI] ;取第1个数据存到BH • JCXZ LAST ;如果过CX = 0,跳转到LAST
1)计数循环(续) • AGAIN: INC SI • CMP BH, VAR[SI] • JAE NEXT • MOV BH, VAR[SI] ;如果CX不为0,继续循环 • NEXT: LOOP AGAIN • LAST: MOV AH, 4CH ;程序结束,退出 • INT 21H • CODE ENDS • END START
(2)条件循环 • 条件循环是通过测试并判断循环终止条件是否成立来控制循环。在许多循环程序实现求解中,其循环次数在设计时无法确定,但可以找到一个终止循环的条件,这样通过条件循环就可以很方便地实现循环控制。
2)条件循环 • 例:编写一程序,统一一个以‘$’结尾的字符串长度,并要求滤去第一个非空字符之前的所有空格。 • 程序源代码: • DATA SEGMENT • STRG DB ‘THE LENGTH…$’ ;定义字符串 • LENG DW ? ;存放字符串长度 • DATA ENDS • CODE SEGMENT • ASSUME CS:CODE,DS:DATA • START: MOV AX, DATA • MOV DS, AX • MOV SI, OFFSET STRG • XOR BX, BX ;BX清0,存放长度统计值 • LOOP: MOV AL, [SI] ;取字符串中的字符到AL • INC SI ;修改指针,指向下一个字符
2)条件循环(续) • CMP AL, 32 ;判断该字符是否为空格 • JZ LOOP ;若是空格,取下一个字符 • NEXT: CMP AL, ‘$’ ;若不是空格,判断是否为‘$’结束符 • JZ EXIT ;是结束符,跳转到EXIT • INC BX ;不是结束符,长度加1 • MOV AL, [SI] ;取下一个字符 • INC SI ;修改指针 • JMP NEXT • EXIT: MOV LENG, BX ;如果结束符,存放长度于内存单元 • MOV AH, 4CH ;结束程序,退出 • INT 21H • CODE ENDS • END START
(3)多重循环 • 多重循环结构是循环程序的循环体中又包含了另一个循环,实现循环嵌套。在编写多重循环程序时,首先要分清内外循环的任务和要求,接着划分内外循环中有规律变化的参数,如哪些是内循环的地址指针、计数器,哪些是外循环的地址指针、计数器。然后分别确定内外循环的控制方法和具体实施。
3)多重循环 • 例:编程统计一个字数组(该数组全部由非零元素组成)中各元素所含有二进制为1的个数及所有元素中1的个数总和。 • 程序源代码: • DATA SEGMENT • ARRAY DW 200, ,502, 106, 600, 800 • LEN EQU ($ - ARRAY)/2 • NUM DB LEN DUP(?) • TOTAL DB ? • DATA ENDS • CODE SEGMENT • ASSUME CS:CODE,DS:DATA
3)多重循环(续) START: MOV AX, DATA MOV DS, AX LEA SI, ARRAY ;数组首地址送SI LEA DI, NUM ;存统计个数缓冲取首地址送DI MOV CX, LEN LOOP1: MOV AX, [SI] ;取数组元素 MOV BL, 0 LOOP2: CMP AX, 0 JE NEXT SHR AX, 1 ;逻辑右移1位,判断该位是否为0 JNC N_CNT INC BL ;累加“1”的个数 N_CNT: JMP LOOP2 NEXT: MOV [DI], BL ;存各元素对应的统计“0”的位数
3)多重循环(续) • ADD SI, 2 • INC DI • LOOP LOOP1 ;外循环控制 • LEA BX, NUM • MOV CX, LEN • MOV AL, 0 • COUNT: ADD AL, [BX] • INC BX • LOOP COUNT • MOV TOTAL, AL • MOV AH, 4CH ;结束程序,退出 • INT 21H • CODE ENDS • END START
3.2.5 子程序设计 把功能相对独立的程序段单独编写和调试,作为一个相对独立的模块供程序使用,就形成子程序 子程序可以实现源程序的模块化,可简化源程序结构,可以提高编程效率 子程序设计要利用过程定义伪指令 参数传递是子程序设计的重点和难点 子程序可以嵌套;一定条件下,还可以递归和重入
1.子程序的基本概念 子程序的常见格式 子程序名 proc ;具有缺省属性的子程序名过程 push ax ;保护寄存器:顺序压入堆栈 push bx ;ax/bx/cx仅是示例 push cx … ;过程体 pop cx ;恢复寄存器:逆序弹出堆栈 pop bx pop ax ret ;过程返回 子程序名 endp ;过程结束
2.子程序的参数传递 入口参数(输入参数): 主程序提供给子程序 出口参数(输出参数): 子程序返回给主程序 参数的形式: ① 数据本身(传值) ② 数据的地址(传址) 传递的方法: 使用寄存器传递参数 使用存储单元传递参数 使用堆栈传递参数
(1)使用寄存器传递参数 例:在内存中有一字单元ADR,存有4位BCD码,要求编写一程序,完成将其转换成4个字节的非组合BCD码,并将结果存放到以BUF为单元首地址的存储区。 程序源代码: DATA SEGMENT ADR DW 3425H BUF DB 4 DUP(?) DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA START: MOV AX, DATA MOV DS, AX MOV AX, ADR MOV DI, OFFSET BUF CALL APART MOV CX, 4 CALL DISP MOV AH, 4CH INT 21H