Transcript 汇编语言课程
汇编语言程序设计 吴 向 军 中山大学计算机科学系 2003.03.20 第6章 程序的基本结构 6.1 源程序的基本组成 6.1.1 段的定义 在定义段时,每个段都有一个段名。在取段名时,要取一个具有一定含义的段 名。段定义的一般格式如下: 段名 SEGMENT [对齐类型] [组合类型] [类别] … ;段内的具体内容 段名 ENDS 其中:“段名”必须是一个合法的标识符,前后二个段名要相同。可选项“对 齐类型”、“组合类型”和“类别”的说明作用请见6.3节中的叙述。 段的长度是指该段所占的字节数: 如果段是数据段,则其长度是其所有变量所占字节数的总和; 如果段是代码段,则其长度是其所有指令所占字节数的总和。 在通常情况下,一个段的长度不能超过64K,在80386及其以后系统的保护方 式下,段基地址是32位,段的最大长度可达4G。 第6章 程序的基本结构 一个数据段的定义例子: 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存储数据段的段值 … MOV AX, 4C00H INT 21H CODE1 ENDS ;调用DOS功能,结束程序的运行 第6章 程序的基本结构 6.1.2 段寄存器的说明语句 在汇编语言源程序中可以定义多个段,每个段都要与一个段寄存器建立一种对 应关系。建立这种对应关系的说明语句格式如下: ASSUME 段寄存器名:段名[, 段寄存器名:段名, ……] 其中:段寄存器是CS、DS、ES、SS、FS和GS,段名在段定义语句说明。 例如, ASSUME CS:CODE1, DS:DATA1 说明了:CS对应于代码段CODE1,DS对应于数据段DATA1。 在ASSUME语句中,还可以用关键字NOTHING来说明某个段寄存器不与任何 段相对应。下面语句说明了段寄存器ES不与某段相对应。 ASSUME ES:NOTHING 在通常情况下,代码段的第一条语句就是用ASSUME语句来说明段寄存器与 段之间的对应关系。 在代码段的其它位置,还可以用另一个ASSUME语句来改变前面ASSUME语 句所说明的对应关系,这样,代码段中的指令就用最近的ASSUME语句所建立的 对应关系来确定指令中的有关信息。 第6章 程序的基本结构 例6.1:汇编语言段及其段说明语句的作用。 DATA1 SEGMENT ;定义数据段DATA1 word1 DW 5678h byte1 DB “ABCDEFG” DATA1 ENDS DATA2 SEGMENT ;定义数据段DATA2 word2 DW 1234h word3 DW 9876h DATA2 ENDS DATA3 SEGMENT byte2 DATA3 ENDS DB ;定义数据段DATA3 ? 第6章 程序的基本结构 CODE1 SEGMENT ;编写代码段CODE1 ASSUME CS:CODE1, DS:DATA1, ES:DATA2 ;① MOV AX, DATA1 ;② MOV DS, AX ;③ MOV AX, DATA2 ;④ MOV ES, AX ;⑤ … MOV AX, word1 ;访问段DATA1中的字变量word1 MOV word2, AX ;访问段DATA2中的字变量word2 … ASSUME DS:DATA3, ES:NOTHING ;⑥ MOV AX, DATA3 MOV DS, AX MOV BL, byte2 ;访问段DATA3中的变量byte2 … MOV AX, 4C00H ;⑦ INT 21H ;⑧ CODE1 ENDS 第6章 程序的基本结构 6.1.3 堆栈段的说明 在源程序中,可用以下方法来定义堆栈段。 方法1: Stack1 SEGMENT DB 256 DUP(?) ;256是堆栈的长度,可根据需要进行改变 TOP LABEL WORD Stack1 ENDS 由于堆栈是按地址从大到小的存储单元顺序来存放内容的,所以,在堆栈存储 单元说明语句之后,再说明一个栈顶别名,这样,对SP的赋值就很方便。 在源程序中,还要添加如下程序段,才能把段Stack1当作堆栈段来使用。 … ASSUME SS:STACK1 ;可在代码段的段指定语句中一起说明 CLI ;禁止响应可屏蔽中断 MOV AX, STACK1 MOV SS, AX MOV SP, offset TOP ;给堆栈段的栈顶寄存器SP赋初值 STI ;恢复响应可屏蔽中断 … 第6章 程序的基本结构 方法2: STACK1 SEGMENT STACK DB ;定义一个堆栈段,其段名为STACK1 256 DUP(?) STACK1 ENDS 上述段定义说明了该段是堆栈段,系统会自动把段寄存器SS和栈顶寄存器SP 与该堆栈段之间建立相应的关系,并设置其初值,而不用在代码段对它们进行赋 值。 除了以上二种方法外,还有一种更简洁的方法来定义堆栈段,有关内容请见第 6.4.2节中的叙述。 第6章 程序的基本结构 6.1.4 源程序的结构 例6.2:在屏幕上显示字符串“Hello, World.” STACK1 SEGMENT STACK ;定义堆栈段STACK1 DB 256 DUP(?) STACK1 ENDS DATA1 SEGMENT ;定义数据段DATA1 如果源程序是一个独立的程序 伪指令END后面可附带一个 伪指令END表示源程序到此为止, MSG DB “Hello, World.$” 或主模块,那么,伪指令END后 在程序中已定义的标号,该标号 汇编程序对该语句之后的任何内容 DATA1 ENDS 面一定要附带一个标号;如果源 都不作处理,所以,通常情况下, CODE1 SEGMENT ;编写代码段CODE1指明程序的启动位置。 程序仅是一个普通模块,那么, ASSUME CS:CODE1, DS:DATA1 伪指令END是源程序的最后一条语 其END后面就一定不能附带标号。 句。 START: MOV AX, DATA1 MOV DS, AX MOV DX, offset MSG MOV AH, 9 INT 21H ;中断21H的9H号功能,显示DS:DX指向的字符串 MOV AX, 4C00H INT 21H CODE1 ENDS END START ;源程序的结束语句 第6章 程序的基本结构 6.2 程序的基本结构 在学习高级语言程序设计时,我们知道了程序的三大主要结构:顺序结构、分 支结构和循环结构。在汇编语言的源程序也同样有此三大结构,所不同的是它们 的表现形式不同。用高级语言编写程序时,由于不使用“转移语句”而使这三种 结构清晰明了。 6.2.1 顺序结构 顺序结构是最简单的程序结构,程序的执行顺序就是指令的编写顺序,所以, 安排指令的先后次序就显得至关重要。另外,在编程序时,还要妥善保存已得到 的处理结果,为后面的进一步处理直接提供前面的处理结果,从而避免不必要的 重复操作。 例6.3 编写程序段,完成下面公式的计算。 A←(X-Y+24)/Z的商,B←(X-Y+24)/Z的余数 其中:变量X和Y是32位有符号数,变量A,B和Z是16位有符号数。 第6章 程序的基本结构 6.2.2 分支结构 例6.5 已知字节变量CHAR1,编写一程序段,把它由小写字母变成大写字母。 例6.6 编写一程序段,计算下列函数值。其中:变量X和Y是有符号字变量。 x 10 Y 30x x 190 x0 0 x 10 x 10 例6.7 把下列C语言的语句改写成等价的汇编语言程序段(不考虑运算中的溢出)。 If (a+b > 0 && c%2==0) a = 62; else a = 21; 其中:变量a、b和c都是有符号的整型(int)变量。 第6章 程序的基本结构 分支伪指令的具体格式如下: 格式2: 格式1: .IF condition 指令序列 .ENDIF .IF condition 指令序列1 .ELSE 指令序列2 格式3: .ENDIF .IF condition1 指令序列1 .ELSEIF condition2 指令序列2 .ENDIF 其中:条件表达式“condition”的书写方式与C语言中条件表达式的书写方式相 似,也可用括号来组成复杂的条件表达式。 第6章 程序的基本结构 条件表达式中可用的操作符有:==(等于)、!=(不等)、>(大于)、>=(大于等于)、 <(小于)、<=(小于等于)、&(位操作与)、!(逻辑非)、&&(逻辑与)、||(逻辑或)等。 若在条件表达式中检测标志位的信息,则可以使用的符号名有:CARRY?(相 当于CF==1)、OVERFLOW?(OF==1)、PARITY?(PF==1)、SIGN?(SF==1)、 ZERO?(ZF==1)等。例如: .IF CARRY? && AX != BX ;检测CF==1且AX!=BX是否成立 ;汇编语言指令序列 .ENDIF 在指令序列中,还可再含有其它的.IF伪指令,即:允许嵌套。伪指令.ELSEIF 引导出另一个二叉分支,但它不能作伪指令块的第一个伪指令。 第6章 程序的基本结构 6.2.3 循环结构 循环结构是一个重要的程序结构,它具有重复执行某段程序的功能。通常,循 环结构包括以下四个组成部分: 循环初始化部分——初始化循环控制变量、循环体所用到变量; 循环体部分——循环结构的主体; 循环调整部分——循环控制变量的修改、或循环终止条件的检查; 循环控制部分——程序执行的控制转移。 第6章 程序的基本结构 一、用循环指令构成循环结构 例6.10:分类统计字数组data中正数、负数和零的个数,并分别存入内存字变量 Positive、Negative和Zero中,数组元素个数保存在其第一个字中。 例6.11:把数组score的平均值(取整)存入字变量Average中,数组以负数为结束 标志。 第6章 程序的基本结构 二、用伪指令实现的循环结构 在宏汇编MASM 6.11系统中,增加了表达循环结构的伪指令:WHILE循环、 REPEAT-UNTIL循环。另外,还增加两个辅助循环的伪指令。 循环伪指令的格式和含义如下: 1、WHILE型循环伪指令 .WHILE condition 循环体的指令序列 ;条件"condition”成立时所执行的指令序列 .ENDW 其中:.ENDW与前面的.WHILE相匹配,它标志着其循环体到此结束。 如果条件表达式“condition”在循环开始时,就为“假”(false),那么,该循环 体一次也不会被执行。 第6章 程序的基本结构 2、REPEAT型循环伪指令 .REPEAT .REPEAT 循环体的指令序列 循环体的指令序列 .UNTIL condition .UNTILCXZ [condition] REPEAT型循环在执行完循环体后,才判定逻辑表达式condition的值。若该表 达式的值为真,则终止该循环,并将执行伪指令.UNTIL[CXZ]后面的指令,否则, 将向上跳转到伪指令.REPEAT之后的指令,为继续执行其循环体作准备。 如果.UNTILCXZ后面没有写逻辑表达式,那么,由.REPEAT-.UNTILCXZ所 构成的循环与用LOOP指令所过程的循环是一致的,它们都是以“CX=0”为循环 终止条件。 如果.UNTILCXZ后面书写了逻辑表达式,那么,该逻辑表达式的形式只能是: “EXP1==EXP2”或“EXP1!=EXP2”。所以,这时由“.REPEAT-.UNTILCXZ condition”所构成的循环就与用LOOPNE/LOOPE指令所过程的循环是一致的,它 们都是以“condition || CX=0”为循环终止条件。 .REPEAT-.UNTIL[CXZ]的循环体也会至少被执行一次。 .WHILE-.ENDW和.REPEAT-.UNTIL[CXZ]的循环体内还可再含有循环伪指 令,这样就构成了循环结构的嵌套。 第6章 程序的基本结构 3、辅助循环伪指令 终止循环伪指令 .BREAK .BREAK .IF condition 该伪指令用来终止包含它的最内层循环。前者是无条件终止循环,后者是仅当 逻辑表达式condition为真时,才终止循环。 .WHILE 1 .REPEAT … … .BREAK .IF condition .BREAK .IF condition … … .ENDW .UNTIL 0 对于以上二个循环,如果没有指令来终止循环的话,它们都将进入死循环状态, 但如果在该层循环体内,存在伪指令“.BREAK .IF condition”的话,那么,当逻 辑表达式condition为真时,该循环就会被终止了。 第6章 程序的基本结构 循环继续伪指令 .CONTINUE .CONTINUE .IF condition 该伪指令用于直接跳转到包含它的最内层循环的计算循环条件表达式的代码处。 前者是无条件转移到计算循环条件表达式的代码处,后者是仅当条件表达式 condition为真时,才进行这样的跳转。 辅助循环伪指令.BREAK和.CONTINUE只能在伪指令.WHILE-.ENDW 和.REPEAT-.UNTIL的循环体内使用。 例6.12 显示9个数字字母’1’~’9’,26个大写字母,和显示任意输入的数字字符, 并用按“回车”键来结束本程序的运行。 第6章 程序的基本结构 6.3 段的基本属性 段定义的一般格式如下: 段名 SEGMENT [对齐类型] [组合类型] [类别] … 段名 ENDS 段属性“对齐类型”、“组合类型”和“类别”要按此顺序说明,但这些选项 可根据需要选择书写。如果源程序中不指定某个属性,那么,汇编程序将使用该 属性的缺省值。 程序中的段名可以是唯一的,也可以与其它段同名。在同一模块中,如果有二 个段同名,则后者被认为是前段的后续,这样,它们就属同一段。 当同一模块出现二个同名段时,则后者的可选项属性要么与前者相同,要么不 写其属性而选用前者的段属性。 略。 第6章 程序的基本结构 6.4 简化的段定义 6.4.1 存储模型说明伪指令 程序存储模式说明伪指令的格式如下: .MODEL 存储模式[,语言类型] [,操作系统类型] [,堆栈类型] 程序可选的存储模式有:TINY、SMALL、COMPACT、MEDIUM、LARGE、 HUGE和FLAT。 伪指令.MODEL必须写在源程序的首部,且只能出现一次,其前内容只能是注 释。 如果用伪指令来指定程序所遵循的语言类型,那么,将不允许子程序的嵌套定 义。 第6章 程序的基本结构 TINY 该存储类型是为编写COM文件类型而设置的。程序员还可用汇编命令行选项 /AT和连接命令选项/TINY来达到此目的。 SMALL 所有的数据变量必须在一个数据段之内,所有的代码也必须在一个代码段之内。 该存储类型是独立汇编语言源程序常用的存储模型。 MEDIUM 所有的数据变量必须在一个数据段之内,但代码段可以有多个。在这种模型下, 数据段寄存器的内容保持不变,转移可以是段间转移。 COMPACT 数据段可以有多个,但代码段只能有一。 LARGE 数据段和代码段都可以有多个,但一个数组的字节数不能超过64KB。 HUGE 数据段和代码段都可以有多个,一个数组的字节数也可以超过64KB。 FLAT FLAT存储模式在创建执行文件时,将使该程序仅含一个包括程序数据和代码 的32位段,并且只能在80386及其以后的计算机系统中运行。该程序的文件类型 为EXE。 第6章 程序的基本结构 6.4.2 简化段定义伪指令 程序存储模式说明伪指令的格式如下: .MODEL 存储模式[,语言类型] [,操作系统类型] [,堆栈类型] 程序可选的存储模式有:TINY、SMALL、COMPACT、MEDIUM、LARGE、 HUGE和FLAT。 伪指令.MODEL必须写在源程序的首部,且只能出现一次,其前内容只能是注 释。 如果用伪指令来指定程序所遵循的语言类型,那么,将不允许子程序的嵌套定 义。 第6章 程序的基本结构 代码段定义 .CODE 作用:说明其下面的内容是代码段中内容。 堆栈段定义 .STACK [堆栈字节数] 其中,“堆栈字节数”可以不写,其缺省值为1024B。 数据段定义 .DATA / .DATA? / .CONST 作用:说明其下面的内容是数据段中的变量定义。 在一个源程序中,可以有多个伪指令“.DATA”定义的数据段,这就好象在源 程序中定义多个同段名的数据段一样。 伪指令“.DATA?”说明下面是一个未初始化数据段的开始, 伪指令“.CONST”说明下面是一个常数数据段的开始。 远程数据段定义 .FARDATA [段名] / .FARDATA? [段名] 其中:“段名”是可选项,如果不指定的话,则该段名就取其缺省段名。 作用:说明一个独立的数据段。 伪指令“.FARDATA?”说明下面是一个未初始化的、独立数据段的开始。 第6章 程序的基本结构 6.4.3 简化段段名的引用 表6.3 小模式下简化段定义的缺省属性表 伪指令 缺省段名 对齐类型 组合类型 类别 段组名 .CODE _TEXT WORD PUBLIC 'CODE' .FARDATA FAR_DATA PARA NONE 'FAR_DATA' .FARDATA? FAR_BSS PARA NONE 'FAR_BSS' .STACK STACK PARA STACK 'STACK' DGROUP .DATA DATA WORD PUBLIC 'DATA' DGROUP .DATA? BSS WORD PUBLIC 'BSS' DGROUP .CONST CONST WORD PUBLIC 'CONST' DGROUP 第6章 程序的基本结构 在汇编程序MASM中,提供了二组简化的代码伪指令:.STARTUP和.EXIT。 .STARTUP——在代码段的开始用于自动初始化寄存器DS、SS和SP; .EXIT——用于结束程序的运行,它等价于下列二条语句: MOV AH, 4CH INT 21h 当使用汇编程序TASM时,以上二条伪指令分别改为:STARTUPCODE和 EXITCODE。 例6.16: .MODEL SMALL .STACK 128 .DATA MSG DB "Simplified Segment Directives.$" .CODE .STARTUP ;自动初始化寄存器DS、SS和SP MOV DX, offset MSG MOV AH, 9H INT 21h .EXIT 0 END 第7章 子程序和库 7.1 子程序的定义 定义子程序的一般格式如下: 子程序名 PROC [NEAR | FAR] … ;子程序体 子程序名 ENDP 对子程序定义的具体规定如下: “子程序名”必须是一个合法的标识符,并前后二者要一致; PROC和ENDP必须是成对出现的关键字,表示子程序定义开始和结束; 子程序的类型有近(NEAR)、远(FAR)之分,其缺省的类型是近类型; NEAR类型的子程序只能被与其同段的程序所调用,FAR类型的子程序可被不 同段的程序所调用; 子程序至少要有一条返回指令。返回指令是子程序的出口语句,但它不一定是 子程序的最后一条语句; 子程序名有三个属性:段值、偏移量和类型。其段值和偏移量对应于子程序的 入口地址,其类型就是该子程序的类型。 第7章 子程序和库 在编写子程序时,除了要考虑实现子程序功能的方法,还要养成书写子程序说 明信息的好习惯。其说明信息一般包括以下几方面内容: 功能描述 入口和出口参数 所用寄存器 ;可选项,最好采用寄存器的保护和恢复方法 所用额外存储单元 ;可选项,可以减少为子程序定义自己的局部变量 子程序的所采用的算法 ;可选项,如果算法简单,可以不写 调用时的注意事项 ;可选项,尽量避免除入口参数外还有其它的要求 子程序的编写者 ;可选项,为将来的维护提供信息 子程序的编写日期 ;可选项,用于确定程序是否是最新版本 这些说明性信息虽然不是子程序功能的一部分,但其他程序员可通过它们对该 子程序的整体信息有一个较清晰认识,为准确地调用它们提供直接的帮助,与此 同时,也为实现子程序的共享提供了必要的资料。 第7章 子程序和库 7.2 子程序的调用和返回指令 7.2.1 调用指令 调用子程序指令格式如下: CALL 子程序名/Reg/Mem 子程序的调用指令分为近(near)调用和远(far)调用。如果被调用子程序的属性 是近的,那么,CALL指令将产生一个近调用,它把该指令之后地址的偏移量(用 一个字来表示的)压栈,把被调用子程序入口地址的偏移量送给指令指针寄存器 IP即可实现执行程序的转移。 第7章 子程序和库 如果被调用子程序的属性是远的,那么,CALL指令将产生一个远调用。这时, 调用指令不仅要把该指令之后地址的偏移量压进栈,而且也要把段寄存器CS的 值压进栈。在此之后,再把被调用子程序入口地址的偏移量和段值分别送给IP和 CS,这样完成了子程序的远调用操作。 例如: CALL CALL CALL CALL CALL CALL DISPLAY;DISPLAY是子程序名 BX ;BX的内容是子程序的偏移量 WORD1 ;WORD1是内存字变量,其值是子程序的偏移量 DWORD1 ;DWORD1是双字变量,其值是子程序偏移量和段值 word ptr [BX] ;BX所指内存字单元的值是子程序的偏移量 dword ptr [BX] ;BX所指内存双字单元的值是子程序的偏移量和段值 第7章 子程序和库 7.2.2 返回指令 当子程序执行完时,需要返回到调用它的程序之中。为了实现此功能,指令系 统提供了一条专用的子程序返回指令。其格式如下: RET/RETN/RETF [Imm] 子程序的返回在功能上是子程序调用的逆操作。为了与子程序的远、近调用相 对应,子程序的返回也分:远返回和近返回。 第7章 子程序和库 如果返回指令后面带有立即数(其值通常为偶数),则表示在得到返回地址之后, SP还要增加的偏移量,它不是类似于高级语言中子程序的返回值。 例如: RET ;可能是近返回,也可能是远返回 RETN ;近返回指令 RETF ;远返回指令 RET 6 ;子程序返回后,(SP)←(SP) + 6 第7章 子程序和库 例7.1:编写一个子程序UPPER,实现把寄存器AL中存放的字符变大写。 解: ;子程序功能:把AL中存放的字符变大写 ;入口参数:AL ;出口参数:AL ;算法描述:判断AL中字符必须在’a’~’z’之间才能把该字符变为大写 UPPER PROC over: CMP AL, ‘a’ JB over CMP AL, ‘z’ JA over SUB AL, 20H RET UPPER ENDP ;书写’a’的ASCII码61H也可以 ;书写指令AND AL, 0DFH也可以 例7.2:编写一个求字符串长度的子程序StrLen,该字符串以0为结束标志,其首 地址存放在DS:DX,其长度保存在CX中返回。 解: ;子程序功能:求字符串的长度 ;入口参数:DS:DX存放字符串的首地址,该字符串以0为结束标志 ;出口参数:CX存放该字符串的长度 ;算法描述:用BX来指针来扫描字符串中的字符,如果遇到其结束标 志,则停止扫描字符串操作 StrLen PROC PUSH AX PUSH BX ;用堆栈来保存子程序所用到的寄存器内容 XOR CX, CX XOR AL, AL MOV BX, DX again: CMP [BX], AL JZ over INC CX ;增加字符串的长度 INC BX ;访问字符串的指针向后移 JMP again over: POP BX ;恢复在子程序开始时所保存的寄存器内容 POP AX RET StrLen ENDP 第7章 子程序和库 7.3 子程序的参数传递 7.3.1 寄存器传递参数 一方面,由于CPU中的寄存器在任何程序中都是“可见”的,一个程序对某寄 存器赋值后,在另一个程序中就能直接使用,所以,用寄存器来传递参数最直接、 简便,也是最常用的参数传递方式。但另一方面,CPU中寄存器的个数和容量都 是非常有限,所以,该方法适用于传递较少的参数信息。 例7.1是用寄存器传递参数的例子,子程序处理的数据被保存在寄存器AL中。 假设有下列的程序段: … 母 MOV CALL … MOV AL, ‘b’ UPPER CALL UPPER ;子返回时,(AL)=’B’ AL, ‘2’ ;子返回时,AL的值不变,因为’2’不是字 第7章 子程序和库 例7.3:按五位十进制的形式显示寄存器BX中的内容,如果BX的值小于0,则应 在显示数值之前显示负号‘-’。 例如:(BX)=123,显示:00123;(BX)=-234,显示:-00234; 解: 见书,略。 第7章 子程序和库 7.3.2 约定存储单元传递参数 在调用子程序时,当需要向子程序传递大量数据时,因受到寄存器容量的限制, 就不能采用寄存器传递参数的方式,而要改用约定存储单元的传送方式。 例7.2是采用约定存储单元传递参数的例子,所处理的数据不是直接传给子程 序,而是把存储它们的地址告诉子程序。 例7.4:编写一个子程序分类统计出一个字符串中数字字符、字母和其它字符的 个数。该字符串的首地址用DS:DX来指定(以0为字符串结束),各类字符 个数分别存放BX、CX和DI中。 解: 见书,略。 例7.5:显示出任意字符串中数字字符、字母和其它字符的个数。 解: 见书,略。 第7章 子程序和库 7.3.3 堆栈传递参数 堆栈是一个特殊的数据结构,它通常是用来保存程序的返回地址。当用它来传 递参数时,势必会造成数据和返回地址混合在一起的局面,用起来要特别仔细。 具体做法如下: 当用堆栈传递入口参数时,要在调用子程序前把有关参数依次压栈,子程序从 堆栈中取到入口参数; 当用堆栈传递出口参数时,要在子程序返回前,把有关参数依次压栈(这里还 需要做点额外操作,要保证返回地址一定在栈顶),调用程序就可以从堆栈中 取到出口参数。 第7章 子程序和库 1、用堆栈传递入口参数的调用方法: … PUSH Para1 … PUSH Paran ;把n个字的参数压栈 CALL SUBPRO ;调用子程序SUBPRO … 第7章 子程序和库 1、用堆栈传递入口参数的调用方法: … PUSH Para1 … PUSH Paran ;把n个字的参数压栈 CALL SUBPRO ;调用子程序SUBPRO … 第7章 子程序和库 2、在子程序中取入口参数的方法 段内调用子程序 SUB1 PROC NEAR PUSH BP MOV BP, SP ;用寄存器BP来访问堆栈,读取参数 … MOV ;保护寄存器BP ;保护其它寄存器的指令 Paran, [BP+4] 从堆栈中取入口参数 … MOV … SUB1 ENDP Para1, [BP+4+2*(n-1)] 第7章 子程序和库 段间调用子程序 在段间调用子程序时,CALL指令会把返回地 址的偏移量和段寄存器CS的内容都压栈。 在进入子程序后,需要用BP来读取传递过来 的参数,所以,也要先保护BP原来的值,再把 当前SP的值传送给BP。 当前BP所指向的堆栈单元与最后一个参数 Paran之间隔着BP的原值、返回地址的偏移量 和段地址,所以,二者之间相差6个字节。 第7章 子程序和库 7.4 寄存器的保护与恢复 在子程序中,保存和恢复寄存器内容的主要方法是:在子程序的开始把它所用 到的寄存器压进栈,在返回前,再把它们弹出栈。这样编写的好处是该子程序可 以被任何其它程序来调用。 在调用指令前,不需要保存寄存器,在调用指令后,也无需恢复寄存器。 利用堆栈来保存和恢复寄存器内容方法的一般形式如下: XXXXX PROC PUSH REG1 ;把所使用的寄存器压栈,REGi代表某个寄存器 … PUSH REGn … ;子程序的处理功能语句 POP REGn ;前面压栈的寄存器弹出,注意它们的次序 … POP REG1 RET XXXXX ENDP 第7章 子程序和库 在子程序中利用堆栈来保存和恢复寄存器内容。利用堆栈来实现此项功能时, 应注意以下几点: 用堆栈保存和恢复寄存器的内容,要注意堆栈“先进后出”的操作特点 通常情况下不保护入口参数寄存器的内容,当然,也可以根据事先的约定而对 它们加以保护 如果用寄存器带回子程序的处理结果,那么,这些寄存器一定不能加以保护 整个子程序的执行几乎肯定要改变标志位,可用PUSHF和POPF来保护和恢 复标志位,但一般在子程序中不保护标志位,除非有此特殊需要 第7章 子程序和库 7.5 子程序的完全定义 7.5.1 子程序完全定义格式 子程序名 PROC [distance] [langtype] [visibility] [<prologuearg>] [USES 寄存器列表] [,参数[:数据类型]]... [LOCAL varlist] 子程序的程序体 子程序名 ENDP 定义子程序时,可使用参数表来直接指明其所要的参数,但程序员必须先 用.MODEL伪指令,或使用<langtype>参数来说明本子程序所使用的程序设计语 言类型。 程序员在定义子程序时,最好先说明该子程序的原型(用伪指令PROTO)。这 样,在调用时,系统可以自动进行类型检查,也可以使用更方便的调用伪指令 INVOKE来调用该子程序。 第7章 子程序和库 7.5.2 子程序的位距 子程序的位距(Distance)有:Near、Far、Near16、Far16、Near32和Far32。 子程序位距描述符告诉汇编程序该子程序是在本段之内(Near),还是在本段之 外(Far)。 7.5.3 子程序的语言类型 子程序语言类型(Language Type)可以是任何一种有效的程序设计语句类型, 由它来告诉汇编程序将使用什么样的标识符的命名风格、子程序的调用和返回约 定。该语言类型说明可使汇编语言程序与其它语言程序达到共享的目的。 程序员可用另外三种方法来设置程序的语言类型:.MODEL、OPTION LANGTYPE:和命令行选项/Gx。 若在程序和命令行中都说明了语言类型,那么,前者的说明优先。 第7章 子程序和库 7.5.4 子程序的可见性 子程序的可见性(Visibility)决定该子程序对其它模块是否可用。它共有三个属 性值:PRIVATE、PUBLIC和EXPORT。 7.5.5 子程序的”起始”和”结束”操作 当程序员想用自己定义的宏来替代缺省的“起始”和“结束”的代码段时,可 用下列说明语句来实现: OPTION PROLOGUE : MacroName1 OPTION EPILOGUE : MacroName2 PROLOGUE和EPILOGUE分别指定MacroName1和MacroName2为“起始” 和“结束”代码段的宏名。 若程序员不要汇编程序自动产生“起始”和“结束”代码,则可用NONE来代 替说明语句中的宏名,即: OPTION PROLOGUE : NONE OPTION EPILOGUE : NONE 第7章 子程序和库 7.5.6 寄存器的保护和恢复 保护寄存器说明子句的说明格式: USES 寄存器列表 该说明子句要求汇编程序为其生成保护和恢复寄存器的指令序列: 在进入子程序执行指令之前,把寄存器列表中的寄存器压进堆栈; 在结束子程序时,把先前压进堆栈的寄存器弹出,以达到保护寄存器的目的。 寄存器列表:列举出在子程序中需要保护的寄存器名,即:在子程序开始时需 要把内容进栈的寄存器名。若有多个寄存器名,则在寄存器名之间要用“空格” 来分开。 第7章 子程序和库 例如: Dsip PROC USES AX DX, FUNC:WORD, MSG:PTR BYTE MOV DX, MSG MOV AX, FUNC INT 21H RET Disp ENDP 汇编程序在处理该子程序时,会根据子句USES的作用,在第一条指令“MOV DX, MSG”之前,插入把寄存器AX和DX进栈的指令序列,即: PUSH AX PUSH DX 而在返回指令RET之前插入把寄存器DX和AX的值弹出的指令序列,即: POP DX POP AX 注意:若子程序含有多个RET或IRET指令,那么,汇编程序在每个RET或 IRET指令前都将增加相应的弹出堆栈指令序列。 第7章 子程序和库 7.5.7 子程序的参数传递 子程序参数是用来向子程序传递信息的数据。若有多个参数,则参数之间要用 逗号分割。为了能说明子程序的参数,程序员必须事先指定参数所遵循的语言类 型或使用“语言类型”参数。 参数的数据类型可以是任何一个有效的数据类型说明符或VARARG。 VARARG数据类型允许向子程序传递“个数”不定的参数,其参数之间要用逗号 “,”来分开。 若参数表中含有VARARG说明的参数,那么,该参数一定是该子程序的最后 一个参数。其规定隐含地说明了在参数表中只能有一个用VARARG说明的参数。 如果没有显式地指定某个参数的数据类型,那么,在16位段规模的情况下,其 缺省的数据类型是WORD;在32位段规模的情况下,其缺省的数据类型是 DWORD。 第7章 子程序和库 7.5.8 子程序的原型说明 子程序原型的说明格式如下: 子程序名 PROTO [distance] [langtype] [,[parameter]:tag]... 该说明语句告诉汇编程序该子程序的若干属性,如:位距、语语言类型、参数 个数及其类型等。这样,汇编程序就可以对其定义进行适当的检查。 如果对所有基于堆栈的过程都定义一个原型,那么,就可把这些原型存放在一 个独立的包含文件(用伪指令INCLDUE来装入)中。使用这种方法对将来把所有子 程序放入自定义的库文件中是非常方便的。 该原型说明语句中参数distance、langtype、parameter和tag等的含义与前面 的叙述相一致。 第7章 子程序和库 7.5.9 子程序的调用伪指令 子程序调用伪指令INVOKE与调用指令CALL在功能上是一致的,但它使汇编 语言的子程序调用方法高级语言化。 调用伪指令INVOKE的使用格式如下: INVOKE expression [, arguments] 其中:expression —— 地址表达式,通常为子程序名; arguments —— 传递给子程序的参数列表,各参数之间用”,”分开, 参数可以是寄存器、表达式或ADDR 标识符等。 该伪指令是调用基于堆栈的子程序的方法,它把所有参数压栈,子程序结束时, 又把参数自动弹出堆栈。 在参数传递时,汇编程序将根据子程序的原型进行数据类型检查。若需要进行 参数类型转换的话,汇编程序则自动生成一段代码来满足数据类型转换的要求。 第7章 子程序和库 例如: INVOKE TEST, AX, 12+34, ADDR MSG 其中:TEST是子程序名,寄存器AX和表达式“12+34”是参数,“ADDR MSG” 是传递变量MSG的地址。 例7.6 编写一个累加参数数值的子程序。其中参数的个数不定,参数的个数由第 一个参数来确定。 解: .MODEL SMALL .STACK 256 .CODE ;第一个参数parmcount确定其后面参数parmvalues中所含参数的个数 ADDUP PROC NEAR C, parmcount:WORD, parmvalues:VARARG XOR AX, AX XOR SI, SI MOV CX, parmcount .REPEAT ADD AX, parmvalues[SI] INC SI INC SI .UNTILCXZ RET ADDUP ENDP .STARTUP INVOKE ADDUP, 3, 5, 2, 4 ;计算5+2+4 INVOKE ADDUP, 4, 1, 2, 3, 4 ;计算1+2+3+4 .EXIT 0 END 第7章 子程序和库 7.5.10 局部变量的定义 局部变量的定义格式: LOCAL 变量名[[数量]] [:数据类型] [,变量名[[数量]] [:数据类型]]... 伪指令LOCAL的作用是说明一个或多个临时的局部变量(位于堆栈中)。局部变 量必须在任何指令之前加以说明,可用多个LOCAL伪指令来说明局部变量。 在子程序中,若说明了某个局部变量,则子程序体中的指令就可使用该局部变 量。汇编程序会把对它的引用转换成用指针寄存器BP来访问其在堆栈中的实际 存储单元。 局部变量只在当前子程序中使用,离开该子程序,它们就不能再被引用。但在 局部变量的命名规则上有所不同,高级语言中的局部变量可与外层变量同名,而 汇编语言中的局部变量不能与其它任何变量同名,否则,在汇编时,将会给出 “重定义”(Symbol redefinition)的错误信息。 第7章 子程序和库 “数量”用来说明该变量所具有的元素个数,该数量必须写在括号“[ ]”之中。 “数量”说明项是可选项。 局部变量的类型说明符可以是任何合法的数据类型说明符。在16位段环境下, 该缺省数据类型是WORD,而在32位段环境下,该缺省数据类型是DWORD。 例如: LOCAL data[20]:BYTE, num:WORD 在上例的说明中,定义了二个局部变量:data和num。前者是字节类型,并有 20个元素,后者是字类型,只有其自身1个元素。 第7章 子程序和库 7.6 子程序库 7.6.1 建立库文件命令 宏汇编MASM系统提供了建立库 文件的命令文件LIB.EXE。其通常 是在命令行环境(MS-DOS方式)下 使用的,当然,也可在Windows操 作系统环境下利用其“开始”菜单 下的“运行”功能项来使用。 一、MS-DOS系统 显示命令LIB用法的命令如下: …>lib /? 该命令的显示结果如右图所示。 第7章 子程序和库 二、Windows系统 第7章 子程序和库 7.6.2 建立库文件举例 假设现有目标文件sub1.obj、sub2.obj和sub3.obj,要用它们建立库文件 mylib.lib。可用下列方法来建立该库文件: 方法1:所有目标文件都准备好了,可一次性把它们加入到库文件中 …>lib mylib +sub1 +sub2 +sub3 方法2:随着目标文件的逐个生成,而依次把它们加入到库文件中 …>lib mylib +sub1 …>lib mylib +sub2 …>lib mylib +sub3 假如源文件sub3.asm已修改,并也生成了新的目标文件sub3.obj,这时,可用 下面命令来实现替换: …>lib mylib -+sub3 当提示目标库文件名(Output library)时,可按“回车”用默认的原库文件名。 如果想查看库文件mylib.lib中文件的大小和存放的先后次序,可用下列命令: …>lib mylib, list ;把库文件mylib.lib中的文件结构生成到文件list中 …>type list 第7章 子程序和库 7.6.3 库文件的应用 当开发一个功能较强、关系较复杂的应用程序时,其执行文件常常由多个目标 文件(模块)连接而成的。各模块之间无疑会存在着相互调用、相互访问数据单元 等内在联系。 为了解决描述各模块之间的联系,汇编语言提供了二条伪指令PUBLIC和 EXTRN,它们的作用说明变量、过程和函数是“全局的”或“外部的”。 这二条伪指令的具体用法和含义如下: 1. 伪指令PUBLIC 伪指令PUBLIC是用来说明:当前模块中哪些标识符是能被其它模块引用的公 共标识符。其说明的一般格式如下: PUBLIC 标识符1, 标识符2, …… 其中:“标识符”可以是变量名、过程名和程序标号,标识符之间要用逗号分开。 上面说明语句说明了标识符1、标识符2等是公共标识符,可以被其它模块引用。 在一个模块中,可用多条PUBLIC伪指令来说明公共标识符。 第7章 子程序和库 2. 伪指令EXTRN 伪指令EXTRN是用来说明:在当前模块所使用的标识符中,哪些标识符是已 在其它模块中被定义为指定类型的标识符。如果当前模块使用了其它模块的标识 符,而对它又不加以说明的话,那么,汇编程序将会给出下列出错信息: error nnnnn: undefined symbol : XXXXXX 伪指令EXTRN的一般说明格式如下: EXTRN 标识符1:类型1, 标识符2:类型2, …… 其中:“标识符”和“类型”之间要用冒号“:”连接。 上面语句说明了标识符1、标识符2等是外部标识符,它们在其它模块中已被分 别定义为类型1、类型2等,该类型说明符可以是:NEAR、FAR、BYTE、 WORD、DWORD等之一。 在一个模块中,可用多条EXTRN伪指令来说明本模块所引用的外部标识符。 注意:伪指令EXTRN中所说明的标识符必须在其定义的模块中被PUBLIC伪指 令说明为公共标识符,并且其说明的标识符类型要与该标识符在定义是的类型相 一致,否则,要么不能生成其可执行文件,要么其执行文件不能正确运行。 第7章 子程序和库 7.6.4 库文件的好处 程序员在编写程序时,通常采用模块化的思想来组织源程序:把各类子程序分 别编写在不同的源程序中,在各源程序中说明所用到的、在其它模块中已定义的 子程序,或说明本模块所定义的子程序可被其它模块调用。这样就可以分别汇编 它们而得到其相应的目标文件,在有了这些目标文件后,程序员就可用不同的方 法来生成最终的可执行目标文件。 方法1:直接连接目标文件而生成可执行文件 这种方法简单、方便,也是常用的一种方法,但在连接时,LINK程序会把目标 文件中的所有代码都嵌入到执行文件中,从而使得:包含在某目标文件中、但并 没有被调用的子程序代码也出现在执行文件中。这种情况无疑增加了执行文件的 字节数。 第7章 子程序和库 方法2:采用子程序库的方法 库文件可以把它看成是子程序的集合。库文件中存储着子程序名、子程序的目 标代码以及连接所需要的重定位信息。 当某目标文件与库文件相连接时,LINK程序只把目标文件所用到的子程序从库 文件中找出来,并合并到最终的可执行文件中,而不是把库中所含的全部子程序 都纳入最后的可执行文件。 对照方法1和2可知:用库文件来存储子程序可生成较短的执行文件。 第8章 输入输出和中断 8.1 输入输出的基本概念 输入输出是一个完整应用程序的重要组成部分,是交互式应用程序不可缺少的 组成部分。在高级语言编程时,程序员可直接用输入输出语句来完成键盘输入、 屏幕显示或打印输出等需求,而无需关心这些输入输出语句是如何实现的。但汇 编语言是与机器有关的程序设计语言,要编写出具有输入输出功能的代码段就必 须清楚CPU为输入输出提供了哪些指令,或计算机系统提供了哪些可直接使用的 功能调用。 8.1.1 I/O端口地址 I/O端口是CPU与输入输出设备的交换数据的场所。通过I/O端口,处理机可以 接受从输入设备输入的信息,也可向输出设备发送信息。 在计算机系统中,用不同的数字给各类I/O端口进行编号,这种I/O端口的编号 就称为I/O端口地址。 第8章 输入输出和中断 在Intel公司的CPU家族中,I/O端口的地址空间可达64K,即可有65536个字节 端口,或32768个字端口。这些地址不是内存单元地址的一部分,不能用普通访 问内存指令来读取其信息,要用专门的I/O指令才能访问它们。虽然CPU提供了 很大的I/O地址空间,但目前大多数微机所用的端口地址都在0~3FFH范围之内, 其所用的I/O地址空间只占整个I/O地址空间的很小部分。 表8.1 几个重要的I/O端口地址 端口地址 端口名称 端口地址 端口名称 020H~023H 中断屏蔽寄存器 378H~37FH 040H~043H 时针/计数器 3B0H~3BBH 单色显示器端口 060H 键盘输入端口 3BCH~3BFH 并行口LPT1 061H 扬声器(0,1位) 3C0H~3CFH VGA/EGA 200H~20FH 游戏控制口 3D0H~3DFH CGA 278H~27FH 并行口LPT3 3F0H~3F7H 磁盘控制器 3F8H~3FFH 串行口COM1 2F8H~2FFH 串行口COM2 并行口LPT2 第8章 输入输出和中断 8.1.2 I/O指令 由于I/O端口地址和内存单元地址是相互独立的,这些端口地址不能用普通的 访问内存指令来访问其信息。 输入指令IN 输入指令IN的一般格式如下: IN AL/AX, PortNo/DX 该指令的作用是从端口中读入一个字节或字,并保存在寄存器AL或AX中。如 果某输入设备的端口地址在0~255范围之内,那么,可在指令IN中直接给出,否 则,要把该端口地址先存入寄存器DX中,然后由DX来给出其端口地址。 例如: IN AL, 60H ;从端口60H读入一个字节到AL中 IN AX, 20H ;把端口20H、21H按“高高低低”组成的字读入AX MOV IN IN AX DX, 2F8H AL, DX ;从端口2F8H读入一个字节到AL中 AX, DX ;把端口2F8H、2F9H按“高高低低”组成的字读入 第8章 输入输出和中断 输出指令OUT 输出指令OUT的一般格式如下: OUT PortNo/DX, AL/AX 该指令的作用是把寄存器AL或AX的内容输出到指定端口。如果某输出设备的 端口地址在0~255范围之内,那么,可在指令OUT中直接给出,否则,要把该端 口地址先存入寄存器DX中,然后在指令中由DX来给出其端口地址。 例如: OUT 61H, AL ;把AL的内容输出到端口61H中 OUT 20H, AX ;把AX的内容输出到端口20H、21H中 MOV DX, 3C0H OUT DX, AL ;把AL的内容输出到端口3C0H中 OUT DX, AX ;把AX的内容输出到端口3C0H、3C1H中 第8章 输入输出和中断 8.2 中断 8.2.1 中断的基本概念 1. 中断和中断源 CPU在执行程序时,是否响应中断要取决于以下三个条件能否同时满足: (1)、有中断请求 (2)、允许CPU接受中断请求 (3)、一条指令执行完,下一条指令还没有开始执行 条件(1)是响应中断的主体。除用指令INT所引起的软件中断之外,其它中断请 求信号是随机产生的,程序员是无法预见的。 程序员可用程序来控制条件(2)的满足与否,即用指令STI和CLI来控制CPU是 否响应可屏蔽的外部中断。但对于不可屏蔽中断和内部中断,程序员是无法控制 它们的,CPU一定会执行这些中断的中断服务程序。 第8章 输入输出和中断 2. 中断向量表和中断服务程序 中断向量表是一个特殊的线性表,它保存着 系统中所有中断服务程序或系统参数的入口地址 (偏移量和段地址)。在微机系统中,该向量表有 256个元素,每个元素占4个字节,总共占1K个 字节。 右图中的“偏移量”和“段地址”是指该中 断服务程序入口单元的“偏移量”和“段地址”。 从图中不难看出:若中断号为n,则在中断向量 表中存储该中断服务程序入口地址的单元地址为: 4n。 第8章 输入输出和中断 表8.2 部分常用的中断号及其含义 中断号 含义 中断号 含义 0 除法出错 8 定时器 1 单步 9 键盘 2 非屏蔽中断 A 未用 3 断点 B COM2 4 溢出 C COM1 5 打印屏幕 D 硬盘(并行口) 6 未用 E 软盘 7 未用 F 打印机 第8章 输入输出和中断 8.2.2 引起中断的指令 1、中断指令INT 中断指令INT的一般格式如下: INT Imm 其中:立即数Imm是一个0~0FFH范围内的整数。 指令执行的步骤: 把标志寄存器压栈,清除标志位IF和TF; 把段寄存器CS的内容压栈,并把中断服务程序入口地址的高字部分送CS; 把指针寄存器IP的内容压栈,并把中断服务程序入口地址的低字部分送IP;对 80386及其以后的CPU,对16位段,压16位IP;对32位段,压32位EIP。 在该指令执行完后,CPU将转去执行中断服务程序。由于有了指令INT,程序员 就能为满足某种特殊的需要,在程序中有目的地安排中断的发生,也就是说,该 中断不是随机产生的,而是完全受程序控制的。 第8章 输入输出和中断 2、溢出指令INTO 当标志位OF为1时,引起中断。该指令格式如下: INTO 该指令影响标志位:IF和TF。 第8章 输入输出和中断 8.2.3 中断返回指令 当一个中断服务程序执行完毕时,CPU将恢复被中断的现场,返回到被中断的 程序中。为实现此功能,指令系统提供了一条专用的中断返回指令。该指令的格 式如下: IRET/IRETD 该指令执行的过程基本上是INT指令的逆过程,具体如下: 从栈顶弹出内容送入IP; 再从新栈顶弹出内容送入CS; 再从新栈顶弹出内容送入标志寄存器; 对80386及其以后的CPU,指令IRETD从栈顶弹出32位内容送入EIP。 第8章 输入输出和中断 8.2.4 中断和子程序调用 中断和子程序调用之间有其相似和不同之处。它 们的工作过程非常相似,即:暂停当前程序的执行, 转而执行另一程序段,当该程序段执行完时,CPU 都自动恢复原程序的执行。 中断和子程序调用在实现方面的主要差异: 子程序调用是程序员在编写源程序时事先安排好的,是可知的,而中断是由中 断源根据自身的需要产生的,是不可预见的(用指令INT引起的中断除外); 子程序调用是用CALL指令来实现的,但没有调用中断的指令,只有发出中断 请求的事件(指令INT是发出内部中断信号,不要理解为调用中断服务程序); 子程序的返回指令是RET,而中断服务程序的返回指令是IRET/IRETD; 在通常情况下,子程序是由应用系统的开发者编写的,而中断服务程序是由系 统软件设计者编写的。 第8章 输入输出和中断 8.3 中断功能的分类 汇编语言程序员常用的这类中断有:DOS功能调用(INT 21H)、BIOS中断、硬 件和外设的中断等。 在用户程序中,若直接通过端口来操作硬件或外设,那么,其处理过程没有额 外的多余工作,处理速度显然是最快的,但这样做,无疑使用户程序具有了很大 的局限性。硬件环境的改变将直接影响程序的正常运行。 若用户程序通过调用DOS功能来实现其所需功能,那么,应用程序与低层硬件 相距较远,操作最终的对象需要经过中间环节,处理速度肯定受到一定的损失, 但这种应用程序适应性强,应用范围广,对硬件的依赖性最小。 由于BIOS介于DOS和具体硬件之间,所以,调用BIOS的功能是一个很好的折 中方案。程序员可在以下三种情况下考虑使用BIOS的功能: 1)、BIOS提供的功能,而DOS没有提供该功能的情况; 2)、不能利用DOS功能调用的情况(可能因为某些具体应用的限制); 3)、基于处理速度的考虑,需要绕过DOS层的情况。 综上所述,可以归纳出如下结论:使用中断的层次越高,它与硬件设备相关程 度就越低,处理速度也就越低,但用户程序的适用范围较广。反之也然。 第8章 输入输出和中断 8.3.1 键盘输入的中断功能 例8.1 用键盘最多输入10个字符,并存入内存变量Buff中,“CR”键结束输入。 解:1、方法1 .MODEL SMALL CR EQU 0DH ;定义“回车”键的符号名 .DATA Buff DB 10 DUP(?) .CODE .STARTUP MOV CX, 0AH LEA BX, Buff .REPEAT MOV AH, 0H INT 16H ;用BIOS中的中断功能 .BREAK .IF AL==CR MOV [BX], AL INC BX .UNTILCXZ .EXIT 0 END 第8章 输入输出和中断 例8.1 用键盘最多输入10个字符,并存入内存变量Buff中,“CR”键结束输入。 解: 2、方法2 .MODEL SMALL .DATA Buff DB 10, ?, 10 DUP(?) ;注意缓冲区的定义方式 .CODE .STARTUP LEA DX, Buff MOV AH, 0AH INT 21H ;用DOS中的功能调用 .EXIT 0 END 第8章 输入输出和中断 8.3.2 屏幕显示的中断功能 1、文本显示方式 文本显示方式是指以字符为最小单位的显示方式,每个字符都是以矩形块形式 显示的。 在常用的文本显示模式(模式3)下,屏幕被划分成25行,每行可显示80个字符, 所以,每屏最多可显示2000(80×25)个字符。为了便于标识屏幕上的每个显示位 置,我们就用其所在行和列来表示之,并规定:屏幕的左上角坐标为(0, 0),右下 角坐标为(24, 79)。 在显示字符时,用一个字节存储该字符的ASCII码,用另一个字节存储的显示 属性,即:显示颜色。 由上面的叙述可知:在80×25的文本显示模式下,满屏可显示2000个字符, 也就需要4000个字节来存储一屏的显示信息。 第8章 输入输出和中断 2、图形显示方式 图形显示是目前最常用的一种显示方式,也是Windows操作系统的默认显示方 式。在该显示方式下,我们可以看到优美的图象、VCD、浏览丰富多彩的网页等。 图形显示的最小单位是象素,对每个象素可用不同的颜色来显示。所以,在显 示缓冲区内记录的信息是屏幕各象素的显示颜色。 由于各种图形显示模式所能显示的颜色和象素是不同的,它决定了显示缓冲区 的存储方式也是不同的。 第8章 输入输出和中断 例8.2 用直接写屏方式在屏幕第5行、第10列以黄色(0EH)显示字符串“Hello”。 解:在文本显示方式下,每行显示80个字符,每个字符占2个字节,所以,显示 一行需要160个字节。若在第m行、第n列位置显示字符,则该位置所对应存 储单元的偏移量为:m×160 + n×2。 例8.3 用“霓虹灯”的显示方式显示字符串“Hello”,按ESC键时结束程序的运行。 解:我们用显示颜色的变化来模拟霓虹灯的显示方式,即用颜色15(亮白)作为字 符的主要显示颜色,再用颜色12(亮红)从左到右逐个扫描。 例8.4 编写一个输入密码的程序,该程序的具体要求如下: 1、每输入一个字符,显示字符”#”表示之; 2、密码最多只有10个字符,多余的按键被丢弃; 3、若输入的字符串为“HELLO”,则以蓝色显示”Welcome…”,否则,以闪烁、 亮红色在显示”Invalid Password”。 解: 第8章 输入输出和中断 例8.5 在256色320×200的图形显示模式下,从屏幕最左边向最右边,依次画竖 线(从顶到底),线的颜色从1依次加1。要求用中断调用的方法来画线。 解: 例8.6 在256色320×200的图形显示模式下,从屏幕顶到屏幕底依次画横线(从最 左边到最右边),线的颜色从1依次加1。要求用直接操作显示缓冲区的方法 来画线。 解: 第8章 输入输出和中断 8.3.3 打印输出的中断功能 8.3.4 串行通信口的中断功能 8.3.5 鼠标的中断功能 8.3.6 目录和文件的中断功能 8.3.7 内存管理的中断功能 8.3.8 读取和设置中断向量 谢 谢 计算机科学系 2003年03月20日