void test char a int b char c ?? mul bh add al bl ???? by L9GDA8DS

VIEWS: 4 PAGES: 154

									第四章 汇编语言程序设计

 由指令助记符、符号常量和标号
等符号书写程序的程序设计语言称
为汇编语言(Assemble Language)。



                            1
  用汇编语言编写源程序,经过汇编程序
和连接程序生成目标程序和执行程序
文件名.ASM           文件名.OBJ              文件名.EXE

汇编语言        汇编        目标     连接          执行
源程序                   程序                 程序




编辑程序        汇编程序            连接程序
EDIT.EXE   MASM.EXE         LINK.EXE


                                            2
 1、用文本编辑程序EDIT.EXE编写汇编语
言源程序,产生扩展名为 .ASM的源文件;
 2、用汇编语言编写的源程序经过汇编程
序MASM.EXE自动翻译成目标程序,产生
扩展名为 .OBJ的目标文件;
 3、用链接程序LINK.EXE将目标程序链接
产生扩展名为.EXE的可执行程序。
 8086的汇编程序为ASM-86
 扩展后的宏汇编程序为MASM-86

                      3
  汇编语言是一种面向计算机的符号语言。由
汇编语言组成的源程序需要由汇编程序将其“汇编”
成机器语言组成的程序。这样,计算机才能执行处
理。汇编程序(MASM.EXE)的主要功能是:
  1、将汇编语言源程序翻译成机器语言;
  2、按程序员指定,分配存储区域(包括程序区,
数据区,堆栈区等);
  3、将各种进位制数据转换成二进制数;
  4、把字符转换成ASCII码;
  5、计算出数值表达式的值;
  6、对源程序进行检查,如果有指令错或程序格
式错则给出相应提示。
                      4
  在8086/8088汇编语言中,有两类汇编语言指令,一类是执行
性指令,另一类是说明性指令。对执行性指令,汇编程序都为
之产生机器指令代码;而说明性指令的作用仅仅是告诉汇编程
序对源程序中的执行性指令应该如何产生代码,或分配存储区。
 1、 执行性指令
   “执行性指令”又称“指令语句”。这类语句经汇编后总
  有目
标程序与之对应,按其与汇编后目标程序对应情况又可分为两
种类型:一般性执行性指令和宏指令。执行性指令的功能主要
由其对应的目标程序在运行时来实现。
(1)一般性执行指令
    这种指令是一条指令对应一条8086/8088的机器指令,即
  指令系统中包括的指令。
( 2)宏指令
  这种指令是由伪操作定义的。宏指令是一般性指令的扩展。    5
 (3)执行性指令的格式
    [标号:] [前缀] 指令助记符 [操作数表] [;注释]
  其中[ ]表示根据不同的指令或不同情况可以任选的部分。
操作数表是由逗号分隔开的多个操作数。
   1)标号 代表“:”后指令的存储地址,供JMP,CALL和
LOOP等指令操作使用。除此之外,它还具备一些其他“属性”
   2)前缀是 8086/8088中一些特殊指令,它们同其他指令配
合使用,如“串操作指令”的重复指令REP。
   3)指令助记符 包括8086/8088指令助记符以及用宏定义
语句定义过的宏指令名。
   4)操作数 对8086/8088的一般性执行指令来说,可以是
一个或二个操作数,这时我们称左边的操作数为目标操作数,
右边的操作数为源操作数,对宏指令来说,可能有多个操作
数。操作数之间有逗号隔开。
   5)注释 以“;”开始,用来简要说明该指令在程序中的
  作                               6

用,以提高程序的可读性。
2、说明性指令(伪指令)
  “说明性指令”又称“指示性语句”。由伪操作
 符定
义,用于程序以源程序方式同“汇编程序”通信。
  程序采用说明性指令表示源程序的起始终止信息、
分段情况、内存结构、打印要求和变量说明等信息。
说明性指令的功能由汇编程序来完成。说明性指令在
汇编时不产生任何代码。
   说明性指令的格式如下:
   [名字] 伪操作指令 [操作数表] [;注释]
   其中“名字”可以是标识符定义的常量名、变量
 名、
过程名、段名以及结构名或记录名等。所谓标识符是
由字母、数字、特殊字符(如?或下划线)等组成的    7
  第一节 汇编语言程序格式
一、示例程序
DATA SEGMENT ;数据段
   NUM DW 0011101000000111B;即3A07H
   NOTES DB ‘The result is :’, ‘$’
DATA ENDS
STACK SEGMENT ;堆栈段
   SAVE DW 100 DUP(?)
STACK ENDS
CODE SEGMENT ;代码段
      assume cs:code,ds:data,ss:stack   8
BEGIN: mov ax, data         ;建立数据段段地址
        mov ds, ax
        mov ax,stack         ;建立堆栈段
       mov ss, ax
       mov dx, offset NOTES  ;显示提示信息
       mov ah, 09h           ;系统调用
       int 21h
       mov bx, NUM           ;将数装入BX
       mov ch, 4             ;共4个16进制数
ROTATE: mov cl, 4            ;cl中为移位位数
        rol bx, cl           ;循环左移4次
        mov al, bl           ;将最高4位移到BL中
      and al, 0fh ;提取al中的低4位(一个16进制数)
                     ;首先提取的是16位数中的高4位 9
       add al, 30h     ;将该数转换为ASCII码
       cmp al, ‘9’      ;是0 – 9 的数吗?
        jle DISPLAY    ;是,转DISPLAY,显示该数
        add al, 07h    ;否,则是A – F之间的数
DISPLAY: mov dl, al    ;显示程序
        mov ah, 2      ;系统调用,输出字符
        int 21h
       dec ch          ;显示字符数减1
        jnz ROTATE     ;不到4个,转ROTATE
       mov ax, 4c00h   ;系统调用,AH=4C,退出并返回DOS
       int 21h
CODE   ENDS            ;代码段结束
       END BEGIN       ;程序结束

                                         10
二、汇编语言的语句
 1、指令语句
   在汇编时产生目标代码,对应着机器的某种操作的语句
称为指令语句。
   如:MOV AX, CX
     ADD AX, datal
2、伪指令语句
   在汇编时不产生目标代码,只为汇编程序提供汇编时所
需要信息的语句称为伪指令语句。
   如:datal DW 12abH
该语句将告诉汇编程序datal定义为一个字12abH,汇编程序将
为它分配一个存储器地址,而且把该存储单元与下一个存储
单元初始化为abH和12H。
                              11
3、宏指令语句
    为了书写方便,把一个汇编语句序列用一条指令代
替,这种指令称为宏指令。由宏指令组成的语句称为宏
指令语句。在汇编时,凡是有宏指令的地方将用相应的
汇编语句序列取代,所以宏指令可以产生目标代码。
   例如:funcal macro x     ;x是形式参数
             mov   ah, x
             int   21h
             endm
则 funcal为宏指令,使用时直接写成:
 funcal 2   ;其中2是宏参数,汇编时产生:
 mov ah, 2
 int 21h
两条语句,并将它们汇编成目标代码。
                                   12
4、汇编语言语句的格式
⑴ 指令语句格式
标号:助记符     操作数,‥,操作数;注释


⑵ 伪 指令语句格式
名字   助记符   参数,‥,参数   ;注释


⑶ 宏指令语句格式
宏指令名   实参数,‥,实参数     ;注释
                           13
三、汇编语言中的几个基本概念
1、标识符
    标识符即标号和名字,标号和名字的区别是
标号后面必须跟冒号,而名字后面没有冒号。一
个标号与一条指令的地址的符号名相联系,即标
号是符号地址。例如上面的示例程序中BEGIN、
DISPLAY、ROTATE是标号。
    名字可以是变量名、段名、过程名等,例如
上面的示例程序中DATA、CODE是段名,NUM、
NOTES是变量名。定义标识符有几点要求。参见
书第89页。
                       14
 2、保留字
  保留字是一个汇编语言中预先保留下来的具有特殊含义的符
号,这些符号不能滥用。
3、数的表示
  ⑴ 常数
   二进、八进、十进和十六进制数,注意十六进制数若以字母
开头,前面要加数字0。缺省情况下是十进制数,但可用伪指令
RADIX n来改变缺省的基数。
  ⑵ 实数(有数学协处理器的机器)
  ±整数部分. 小数部分 E±指数部分
  例如:实数5.213E-6 = 5.213×10-6
  ⑶ 字符串常数:用单引号括起来的一个或多个字符组成一个
字符串常数,例如:‘The rezult is:’在内存中。以字符的ASCII
  码
值存放,注意一个空格也是一个字符。如‘The ’,在内存中是
54H、68H、65H和20H。                    15
4、变量
  变量是代表存放在某些存储单元的数据。这些数据
在程序运行期间随时可以修改。
  变量有三个属性:段属性、偏移属性和类型属性。
⑴ 变量的定义与预置
 ① 定义变量就是给变量分配存储单元,且对该存储
单元赋于一个符号名—变量名,同时预置初值。定义
变量用数据定义伪指令DB、DW、DD、DQ、DT等。
例1:VAR_DATA     SEGMENT
        data1   DB 12H
        data2   DW 5678H
    VAR_DATA    ENDS
                           16
② 变量的属性
  ⅰ 段属性(SEG):表示变量存放在那一个逻辑段中,例
如上面定义的变量名data1和data2存放在VAR_DATA逻辑段
中。对它们进行存取时要先将它们所在段的段基值放在DS
中,即执行下面语句:
     mov ax, VAR_DATA
     mov ds, ax
  ⅱ 段的偏移属性(offset):表示变量在逻辑段中离段起点
的字节数。如上面的data1的偏移量为0,data2的偏移量为1。
变量的段属性和偏移属性构成了变量的逻辑地址。
  ⅲ 类型属性(type):表示变量占用存储单元的字节数
  DB 1字节,DW 2字节,DD 4字节,
  DQ 8字节,DT 10字节
                                17
⑵ 数据定义伪指令
格式: 变量名 伪指令名 表达式1,表达式2,‥‥
这里表达式有以下几种情况:
① 数值表达式
 例2: DA_BYTE DB 50H , 50 , 0caH
      DA_WORD DW 0a3f1H , 498dH
 ② ? 表达式,不带引号的?表示可预置任何内容
 例3: DA_B DB ? , ? ;要求分配两个字节单元
      DA_W DW ? , ? ;要求分配两个字单元
 ③ 字符串表达式
  数据项可以写成字符串形式,但只能用DB、DW、DD定
义,而且DW、DD语句定义的串只允许包含两个字符。参看
下面的例子。
                              18
  例4: S1     DB ‘ABCDEF’
     S2  DW ‘AB’, ‘ CD’, ‘ EF’
     S3  DD ‘AB’, ‘ CD’
  这几个变量在存储器中存放情况如下:
  S1   41H   S2   42H      S3 42H
       42H        41H         41H
       43H        44H         00H
       44H        43H         00H
       45H        46H         44H
       46H        45H         43H
                              00H
                              00H
 注意:定义多于两个以上字符的字符串时,
只能使用DB伪指令,不能使用DW和DD等伪指令。            19
  可以用DW语句把变量或标号的偏移地址存入存储器;也
可用DD语句把变量或标号的段地址和偏移地址都存入存储
器,此时低位字存偏移地址,高位字存段地址,例如:

VAR      DW 1234H      PRV   VAR的偏移地址
LABEL:   MOV AL, 04H
    …                        VAR的段地址
PRV      DD   VAR      PRL
PRL      DW   LABEL          LABEL的偏移地址
PRR      DD   LABEL    PRR
                             LABEL的偏移地址


                             LABEL的段地址
其存放格式如右图所示。
                                    20
  ④ 带DUP表达式 , DUP是定义重复数据操作符
格式:
 变量名 数据定义伪指令 表达式1 DUP(表达式2)
 其中表达式1是重复次数,表达式2是重复内容。
例5: D_B1   DB 20H DUP(?) ;保留20H个字节
     D_B2 DB 10H DUP(‘ABCD’);字符串
  ‘ABCD’
                             重复10H次
     D_W1  DW 10H DUP(4) ;字4重复10H次
    ARRAY DB 100 DUP(0,2 DUP(1,2),0,3)
       ;定义ARRAY700个字节
  ⑤ ‘$’符号,表示地址计数器的当前值
例6:ARRAY DW 1,2,$+4, 3,4, $+4
   如果在汇编时,ARRAY的偏移地址是0074H,则在
ARRAY数组中,两个$+4得到的结果是不同的,这是由
                                     21
于$的值是在不断变化的。此定义在存储器中的表示如
右图所示。               ARRAY   01H   0074H
  $用在伪操作的参数字段               00H
时,和用在指令中的情况                 02H
是不同的,用在伪操作中它                00H
表示的是地址计数器的当                 7CH   0078H
前值。用在指令中它只表                 00H
示该指令的首地址,而与                 03H
$本身所在的字节无关。                 00H
例如指令 JNE $+6, 表示            04H
                            00H
满足条件时转移到该指令
                            82H   007EH
的首地址加6以后所在的单元。
                            00H
1000:2543 JNE $+6
则转移地址是2549H
                                      22
例7:下面的数据段定义,COUNT 表示什么?
  DATA SEGMENT
  BUF   DB  ‘0123456789ABCD’
  COUNT EQU $ - BUF
  DATA ENDS
 这里,COUNT的值就是数据区的长度,所以COUNT=14
5、标号
  标号是一条指令的目标代码的符号地址,它常用
作转移指令(或子程序调用指令)的操作数。
  标号有三个属性:段属性、偏移属性和类型属性。
段属性是该标号的段地址,偏移属性是该标号的偏移
地址,类型属性是表示该标号的范围是本段(NEAR)
或段间(FAR),或称为近或远属性。     23
四、表达式和运算符
表达式由常数、操作数、操作符和运算符组成。有六种运
算符,即算术运算符、逻辑运算符和关系运算符、分析运算
符、综合运算符和分离运算符。
1、算术运算符
+、-、*、/、MOD(取余)、SHL(左移)、SHR(右移)
例1 :32 MOD 5  ;结果为2
    21H SHL 2 ;结果为84H
2、逻辑运算符(按位操作)
AND(与)     24H AND 0FH = 04H
OR (或)     24H OR 0FH = 2FH
XOR (异或)   24H XOR 0FH = 2BH
NOT (非)    NOT 24H = 0DBH      24
 3、关系运算符
 关系运算是逻辑判定,当为真时结果为全1(0FFFFH)
,为假时结果为全0。
 EQ (等于)           ;若PP=25,则25 EQ PP = 0FFFFH
 NE (不等于)          ;25 NE PP = 0
 LT (小于)           ;25 LT 26 = 0FFFFH
 LE (小于等于) ;25 LE PP = 0FFFFH
 GT (大于)          ; 26 GT PP = 0FFFFH
 GE (大于等于) ; 24 GE PP = 0
关系运算符一般不单独使用,常与其它运算符结合使用。
例如:ADD AX, ((port GT 60) AND 50) OR ((port LE 60) AND 70)
当port 的值大于60时,上述指令汇编为 ADD AX, 50
当port 的值小于或等于60时,上述指令汇编为 ADD AX, 70
                                                       25
4、分析运算符
  把存储器地址操作数分解成它的组成部分。
SEG (求段地址),   格式: SEG ﹤符号名﹥
OFFSET(求偏移地址),格式 : OFFSET ﹤符号名﹥
TYPE (求符号名类型值),格式:TYPE ﹤符号名﹥


 类型   byte   word dword qword tbyte   NEAR        FAR

类型值    1      2     4     8    10     -1 (FFH)   -2 (FEH)

 SIZE ,求为符号名分配的字节数
    格式: SIZE ﹤符号名﹥
    回送分配给该符号名的字节数,但此值是LENGTH的值
和TYPE的值的乘积。
                                                            26
LENGTH,求为符号名分配的项数。
   格式: LENGTH ﹤符号名﹥
 这里为符号名定义的数据项必须是用 重复格式DUP( )
定义的。而对于其他情况则回送1。
例2 :K2 DW 10 DUP (?)
     则 LENGTH K2 = 10
        TYPE K2 = 2
        SIZE K2 = 20
例3:AARR DW 2,4,6
     则 LENGTH AARR = 1, TYPE AARR = 2
       SIZE AARR = 2
可以看出:
SIZE <符号名> = ( LENGTH <符号名>) * ( TYPE ﹤符号名﹥)
                                         27
5、综合运算符
⑴ 定义符号名为新类型
格式: ﹤类型﹥ PTR ﹤符号名﹥
PTR的功能归纳如下:
① 保证运算时操作数类型的匹配
例3: F1   DB   16H,38H
    F2 DW     1234H,26ABH
    MOV AX,WORD PTR F1      ;AX=3816H
    MOV BL,BYTE    PTR F2   ;BL=34H

                                        28
② 通过PTR指明是字单元还是字节单元
例4:MOV BYTE PTR [BX],10H;[BX]为字节单元
   MOV WORD PTR [SI],20H;[SI]为字单元
 ③ 用PTR来改变距离属性
例5:JMP FAR PTR LLOOP
   CALL DWORD PTR [BX]
⑵ 指定新类型
 格式: ﹤符号名﹥ EQU THIS ﹤类型﹥
例6: LABC EQU THIS BYTE ;LABC是字节类型
    LABD DW 4321H, 2255H;LABD是字类型
    MOV AL , LABC        ;AL = 21H
    MOV AX , LABD        ;AX = 4321H
                                       29
6、分离运算符
 ⑴ 取低字节 格式:LOW ﹤符号名﹥
 ⑵ 取高字节 格式:HIGH ﹤符号名﹥
例7:设SSY=2050H
    mov al , LOW 3080H ; al = 80H
    mov ah , HIGH SSY ; ah = 20H
    mov cl , LOW 3a4bH ; cl = 4bH
7、专用运算符 ,结构和记录中用,后面讲,
 参见书P91页
8、其它运算符 ,参见书P91页
9、汇编运算符的优先级 ,参见书P92页                30
       第二节          伪指令
  伪指令没有对应的机器指令,不是由CPU执行,而是
由汇编程序识别,并完成相应的功能。伪指令越多,应用
越方便,MASM86的伪指令可分为14类。
1、符号定义伪指令
   ﹤符号名﹥ EQU ﹤表达式﹥
   ﹤符号名﹥ =          ﹤表达式﹥
 例1: port1   EQU 78
     port2   EQU port1+2
     counter EQU cx      ;定义为寄存器
     cbd     EQU DAA     ;定义为助记符
      A      = 6
      A      = A+2
                                   31
 = 和 EQU的区别是用=定义的符号名在同一程
序中可以重复定义,而用 EQU定义的符号名在同
一程序中不允许重复定义。

 EQU与特殊运算符PTR或THIS连用,可以给变
量或标号定义新的类型属性并重新命名,但其段
属性和偏移属性不变。




                        32
例2:某一程序段中有以下几条伪指令语句,试分析个变量的
    属性。
DATA    SEGMENT
DDBUF   EQU THIS DWORD
BUF     DB   100 DUP(?)
DWBUF   EQU WORD PTR BUF
DATA    ENDS
        …

FIRST   EQU THIS FAR   ;定义远标号,其段属性和偏移
        LEA SI,BUF     ;属性与紧跟其后指令的存储地
                       ;址相同
        …
DDBUF,BUF,DWBUF分别是双字类型、字节类型和字类
型,它们为同一数据区的首址
                                   33
2、内存数据定义伪指令
  DB 定义字节(前面已讲过定义方法)
  DW 定义字(2字节)
  DD 定义双字(4字节)
  DF 定义6字节
  DQ 定义8字节
  DT 定义10字节
3、段定义伪指令
 ﹤段名﹥ SEGMENT [定位方式] [连接方式] [‘类别名’]
           …
 ﹤段名﹥ ENDS
 ⑴ 定位方式    PAGE   指定起始地址的低8位是0
           PARA   指定起始地址的低4位是0(隐含)
           WORD   指定起始地址的最低位是0
           BYTE   指定起始地址是任意值
                                      34
 ⑵ 连接方式(组合类型)
 组合类型告诉连接程序本段与其它段的关系。有NONE ,
PUBLIC , COMMON , STACK , MEMORY 和 AT 6种。
缺省下是NONE,表示 本段与其他段逻辑上不发生关系。
(参看书上第97页)。
⑶ ‘类别名’
 类别名是用单引号括起来的字符串,连接程序只使同类别的
段发生关联。

4、段寄存器说明伪指令
  ASSUME 段寄存器:段定义名1[,段寄存器:段定义
         名2,…]
 例3:ASSUME CS:CODE, DS:DATA, ES:DATA,
           SS:STACK                35
5、过程(子程序)定义伪指令
﹤过程名﹥ PROC [NEAR或FAR]   ; NEAR可省略
       …
﹤过程名﹥ ENDP
调用过程的格式为:
CALL ﹤过程名﹥


过程返回
RET        ;段内返回
RETF       ;段间返回


                                36
6、模块定义伪指令
  格式: NAME 模块名
      …
      END [ 启动标号或过程名]
 如果不用此定义,则文件名即为模块名

7、定位伪指令
   格式: ORG ﹤表达式﹥
   这里表达式是一个无符号数,表示以下的程
序或数据的开始地址。
 例4:下面程序段,指出变量BUF和NUM的偏移
地址为多少?
                           37
 DATA     SEGMENT
          ORG 10
 BUF DB   ‘ABCD’
          ORG $+5
 NUM      DW     50
 DATA     ENDS
  变量BUF的偏移地址为10;变量NUM的偏移地址为19
8、列表伪指令
   ⑴ 建立标题, 格式: TITLE 标题
   ⑵ 建立小标题,格式: SUBTTL 小标题
   ⑶ 自动排版, 格式: PAGE 行数,行字数;缺省值为66
行80列
 9、系统隐含进位制伪指令
   格式: RADIX 表达式
   定义隐含进位制方式,表达式的值为2-16之间的十进制数 38
10、模块连接伪指令
⑴ 公用符号伪指令
 格式:PUBLIC ﹤符号名1 [,符号名2 ,…]﹥
 说明本模块中定义的符号名可由其它程序模块引用
⑵ 引用符号伪指令
格式: EXTRN ﹤符号名1:类型 [,符号名2:类
型 ,…]﹥
 说明本模块引用的其它模块定义的符号名
 有些版本该伪指令是EXTERN
⑶ 插入伪指令
 格式:INCLUDE ﹤模块名﹥
说明本模块汇编时,把另一模块插入一起汇编。
                               39
⑷ 合段伪指令
  格式: 组名 GROUP ﹤段名1 [,段名2 ,段名3,…]﹥

 例5: PUBLIC INFO
 INFO PROC FAR
      …
 INFO ENDP
 说明过程INFO可以被其它模块调用。

 在另一模块中要调用INFO过程,必须在该模块开始使用
以下语句:
 EXTRN INFO:FAR

                                40
11、记录伪指令
⑴ 记录定义伪指令
格式: 记录名 RECORD ﹤字段名1:字段宽度 [=表达式],
           字段名2:字段宽度[=表达式],…]﹥
 字段宽度表示相应字段所占的位数,取值在1-16之间


⑵ 记录存储单元分配及赋值(定义记录变量)
格式:记录变量名 记录名 ﹤字段值表﹥
 记录变量名是自定义标识符,记录名在前面已定义,则
字段值表中各字段值赋给记录的各字段,若字段值中某些
项缺省,则缺省值为0。
                               41
例6:TAN      RECORD        X : 6 ,Y : 4 , Z : 6
      AP    TAN           < 5 , 10 , >
      BP    TAN            < 12 , , 20 >
 此例中,存储器中分配了两个记录,每个记录是两个
字节。


例7:
PERSON     RECORD     YER : 7 , SEX : 1 , STAU : 2=0
 PV1 PERSON         < 0101000B, 1B , 10B >
 PV2 PERSON         < 0101001B, 0B , >
 PV3 PERSON         100 DUP ( < > ) ;预置100个记录变量
                                                       42
⑶ 记录操作符
  ① WIDTH   格式:WIDTH         记录名/字段名
                                                 记录
  用该操作符求出记录或字段所占的位数
  例8: MOV      AX, WIDTH TAN         ; AX=16     字段
     MOV       BH, WIDTH     Y       ; BH=4
      MOV      DX, WIDTH     PERSON ; DX=10
  ② MASK      格式:MASK 记录字段名
  该操作符返回一个8位或16位的二进制数,该数中相当于该字段的各位
为1,其余各位为0。指令中可以直接引用记录字段名,返回一个立即数,
表示该字段移到所在记录的最右边所需要的移位次数。
 例9: MOV       AL , MASK Z ; AL←00111111B ;最低6位为1
     MOV       BL , MASK X   ; BL ←11111100B ;最高6位为1
     MOV       CX , YER      ; CX ←3,表示移到最右边的位数
     MOV    DX , MASK YER    ; DX ←0000001111111000B
                                                       43
12、结构伪指令
 ⑴ 结构的定义
 格式: 结构名    STRUC
       < 数据定义语句序列 >
     结构名   ENDS
 例10:STU   STRUC
           NO       DB   ?
           NAME     DB   5 DUP(?)
           SCORE    DB       0
     STU   ENDS
 结构定义并不为结构分配存储空间,只是在汇编时进行
结构说明。只有定义了结构变量后,才真正分配内存。
                                    44
⑵ 结构变量的定义
  格式:结构变量名 结构名 < 字段值表 >
  字段值表中各字段值赋给结构变量的各字段中,若字
段值表中某些项缺省(仅写一个逗号),则保留初始值。
例11:SVAR1 STU < 1 , ‘ZHANG’ , 98 >
    SVAR2 STU < 2 , ‘LIHUA’ , >
    SVAR3 STU 50 DUP (< >) ;预置50个结
                                构变量
⑶ 结构变量的引用
  格式:结构变量名 . 结构字段名
例12: MOV AL , SVAR1. NO    ;AL=1
     MOV BX , OFFSET SVAR2
     MOV AH , [BX] . NO    ;AH=2
                                   45
13、块注释伪指令
   格式:COMMENT 定界符 注释 定界符
例13:
  COMMENT / 注释文 /
  COMMENT伪指令用于书写大块注释。
14、LABEL伪指令
  格式:变量/标号名 LABEL       类型
 给已定义的变量或标号取一个别名,并可重新定义他
的属性。
例14:VARB LABEL BYTE
    VARW DW 4142H,4344H
    PTRF  LABEL FAR
    PTRN:MOV AX,[DI]
 给变量VARW取一个新名字VARB(属性为字节)
 给标号PTRN取一个新名字PTRF(为远属性)   46
           第三节 宏指令
 扩展后的汇编语言(ASM)称为宏汇编语言(MASM),宏
汇编语言中可以使用宏指令。宏指令使用时要经过三个步骤:
宏定义、宏调用和宏扩展。
一、宏定义
 格式: 宏指令名      MACRO [形式参数1,形式参数2,…]
               宏体(指令序列)
               ENDM
 其中形式参数是任选项,可以用来代替宏体中某些参数或
符号。在代换指令中的符号时,在其前面要加一个宏代换符&。
例1:AHH   MACRO X , Y
         MOV   CL , X
         RO&Y CL
         ENDM                          47
 SHIFT   MACRO X , Y , Z
         MOV CL , X
         S&Z  Y , CL
         ENDM
二、宏调用
  在程序中直接引用宏指令名称为宏调用 ,宏调用时形式参数要
用实际参数代替,顺序应与形式参数的顺序相同。
  例2: AHH   5,R
      SHIFT 4 , AL , AL
      SHIFT 6 , BX , AR
三、宏扩展
  有宏指令的汇编语言源程序在汇编后,在引用宏指令的地方插
入宏体,这称为宏扩展。
  例3:上述调用宏指令后,宏扩展如下:
     + MOV CL , 5
     + ROR CL
                                48
    + MOV CL , 4
    + SAL AL , CL
    + MOV CL , 6
    + SAR BX , CL
前面的加号是宏扩展的符号。
四、其它宏指令语句
  ⑴ 局部标号定义语句
   格式:LOCAL 标号1[,标号2,…]
 该语句用于定义宏体中定义的标号。LOCAL语句必须是
MACRO后的第一个语句。
  ⑵ 注销宏定义语句
   格式:PURGE 宏指令名1[,宏指令名2,…]
  ⑶ 退出宏定义
   格式:EXTIM
  在宏体或重复定义的语句中遇到EXTIM时,终止以后的宏
 扩展。                        49
⑷ 重复定义伪指令
格式: REPT    表达式
       指令序列
     ENDM
  这里表达式的值是指令序列的重复次数。
例: X=0
   REPT 10
   X=X+1
   DB   X
   ENDM
 该例中定义了10个连续的字节单元,初始值分别为1到10


                           50
⑸ 带立即数的重复定义伪指令
  格式:IRP       形式参数,< 参数表>
           指令序列
      ENDM
  功能:重复执行指令序列,每次重复,将一个实际参
数代入形式参数中,直至最后一个参数代替完为止。
  例4:IRP X , <1,2,3,4,5,6,7,8,9,10>
               DB X
      ENDM
  此例是把1到10分配给10个连续的字节单元。
 ⑹ 带字符串的重复定义伪指令
  格式:IRPC       形式参数,字符串/<字符串>
      指令序列
     ENDM                           51
例5:回车换行宏指令
   funcal   MACRO x
            mov      ah, x
            int      21h
            endm
   crlf     MACRO
            mov      dl, 0dh ;回车的ASCII码是0dh
            funcal 2
            mov      dl, 0ah ;换行的ASCII码是0ah
            funcal 2
            endm
例6 把字符A到Z的ASCII码填入数组TABLE。
   CHAR = ‘A’
   TABLE       LABEL BYTE
               REPT       26
               DB    CHAR
   CHAR = CHAR+1
               ENDM                           52
例7 用宏指令定义两个字操作数相乘,得到一个16位的
第三个操作数作为结果。
 宏定义:
 MULTIPLY     MACRO     OPR1、OPR2、RESULT
              PUSH      DX
              PUSH      AX
              MOV       AX,OPR1
              IMUL      OPR2
              MOV       RESULT,AX
              POP       AX
              POP       DX
              ENDM
宏调用: MULTIPLY      CX,VAR,XYZ[BX]
       MULTIPLY    240,BX,SAVE         53
宏展开:   …
 1     PUSH   DX
 1     PUSH   AX
 1     MOV    AX,CX
 1     IMUL   VAR
 1     MOV    XYZ[BX],AX
 1     POP    AX
 1     POP    DX
       …
 1     PUSH   DX
 1     PUSH   AX
 1     MOV    AX,240
 1     IMUL   BX
 1     MOV    SAVE,AX
 1     POP    AX
 1     POP    DX
       …
                           54
 第四节        BIOS和DOS中断
  BIOS和DOS中断是系统服务软件的集合,它们使用
户可以很方便地访问和使用PC机的硬件。



     高级语言        应用程序
     DOS
     BIOS

         PC机硬件
                          55
一、BIOS基本输入输出系统
  PC机系统板上地址为0FE000H开始的8KB为BIOS
ROM,驻留在该ROM中的BIOS例行程序提供了系统加
电自检、引导装人入以及对I/O接口控制等功能。
  BIOS由许多功能模块组成,每个功能模块的入口地
址都在中断向量表中,调用格式如下:
   ;设置入口参数(在AL中)和功能号(在AH中)
   INT  中断号
 其中中断号为10H-1AH,参见书上P307页附录四。入
口参数放在AL中,功能号放在AH中。
 例如: 中断号 10H    ;功能 :显示器中断
       中断号 16H  ;     键盘中断
                                 56
二、DOS中断和DOS功能调用
  1、 DOS中断
   INT 20H ;程序正常退出
  INT  21H ;系统功能调用
  INT  22H ;结束退出
  INT  23H ;Ctrl-Break处理
  INT  24H ;出错退出
  INT  25H ;读盘,AL=盘号,CX=读入扇区数
      DX=起始逻辑扇区号,DS:BX=缓冲区地址
  INT  26H ;写盘
  INT  27H ;驻留退出
 这些中断中主要是INT 21H的功能调用,有几十个子
程序,参见P302页附录三。
                            57
 2、DOS功能调用
  DOS功能调用的几十个子程序共用一个中断号21H,其
功能号为0-62H(参见P302页附录三)。调用时格式为:
 ⑴ 传送入口参数到指定寄存器中
⑵ 传送功能号到AH寄存器中
⑶ 执行INT 21H

三、键盘输入输出中断
 ⑴ BIOS键盘输入输出中断
键盘上有三类键:
  字符数字键,给计算机传送一个ASCII码字符;
  扩展功能键,产生一个动作,如F1、Home等键;
  和其他键组合使用的控制键,如Alt、Ctrl、Shfit等。
                                   58
   键盘和主机通过5芯电缆连接,他们是:电源线、地线、
复位线、键盘数据线和键盘时钟线。
   键盘内有单片机8048来控制对键盘的扫描。按键的识别
采用行列扫描法,即根据对行列的扫描结果来确定按下键
的位置,并将该键的扫描码送往主机。
  INT 16H ;AH中为功能号
AH (功能号)       功能       返回参数
 0         从键盘读入一字符    AL=字符码
                       AH=扫描码
 1         读键盘缓冲区的字符 如ZF=1 AL=字符码
                           AH=扫描码
                     如ZF=0 缓冲区空
 2          取键盘状态字节   AL=键盘状态字节
   键盘状态字节见下页图。
                              59
60
61
例1: mov     ah , 0
      int 16h
     mov bx , ax
     call binihex ;binihex是二进制转换为16进制子程序,并
把AH、AL中的内容打印出来,AL=字符ASCII码,AH=字符的扫描
码
⑵ DOS键盘功能调用
  INT 21H    ;AH中为功能号
AH     功能             调用参数          返回参数
01    键盘输入并回显                      AL=输入字符
02    显示输出           DL=输出字符
06    直接控制台I/O       DL=0FFH(输入)   AL=输入字符
                     DL=字符(输出)
 07   键盘输入不回显                      AL=输入字符
 08   键盘输入不回显(检测Ctrl+Break)        AL=输入字符
                                             62
 09   显示字符串              DS:DX =串地址,‘$’结束字符串
 0A   键盘输入到缓冲区           DS:DX =缓冲区首地址
                        ( DS:DX )= 缓冲区最大字符数
                        ( DS:DX +1 ) = 实际输入字符数
  0B 读键盘状态                       AL=0FFH 无键入
                                 AL=0 有键入
  0C 清除键盘缓冲区,并调用一种键盘功能
           AL = 键盘功能号(1,6,7,8或A)
例2:单字符输入,判断输入是Y还是N ?,注意大写和小写字母的不同。
    GET_KEY: MOV AH , 1
              INT 21H
              CMP AL , ‘Y’
              JE  YES
              CMP AL , ‘N’
              JE  NO
              JNE GET_KEY
  若检测RETURN(Enter)回车键,则指令中要写它的ASCII码0DH。
                                            63
例3:要求程序能出现一个菜单,               JE    OP3   ;是,转OP1
用户通过键入F1、F2、F3来选择1、           JMP   ERROR
2或3菜单                        注:F1、F2、F3的扫描码分
项,程序检测部分如下:                  别是3BH,3CH,3DH。
       MOV   AH, 7           接收功能键时需两次调用
       INT   21H             AH=7的功能,第一次调用
       CMP   AL, 0;是功能键吗?    返回AL=00,第二次调用返回
       JE GET_EC;是,读扫描码
                             AL=功能键的扫描码。
       JMP ERROR;否,显示错误
GET_EC:MOV   AH , 7
        INT  21H
        CMP AL, 3BH;是F1键吗?
        JE   OP1 ;是,转OP1

      CMP   AL, 3CH;是F2键吗?
      JE     OP2 ;是,转OP2
      CMP   AL, 3DH;是F3键吗?
                                              64
 例4:输入字符串时,字符串存入用户定义的缓冲区,缓冲区的第一个
字节保存缓冲区的最大字符数,这个数由用户程序给出;缓冲区第二个
字节存放实际输入字符数,由功能0AH调用时填入。在这两个字节后是字
符串存放区,字符串最后以回车符0DH结束。
 ⑴ 数据区定义的缓冲区如下:
   MAXLEN          DB    20H
   ACTLEN          DB    ?
   STRING          DB    20H DUP (?)
⑵ 输入字符串的指令如下:
   LEA      DX,    MAXLEN
   MOV      AH,    0AH
   INT      21H
 如果我们键入字符串By brooks too broad for leaping(回车),则缓冲区内
容为 20 1F By brooks too broad for leaping 0D,实际上缓冲区占34个字节,
能输入31个字符。                                            65
例5:从键盘输入字符串程序                        sub ch, ch
Dseg     segment                     mov cl, user_string+1;实际字
 user_string     db 50,0,50dup(?)                         符数送cl
Dseg     ends                        add dx, 2;指向第一个字符
Cseg     segment                       ret
  assume cs:cseg, ds:dseg           Read_keys endp
Read_keys        proc     far       ; …
     push        ds                 cseg ends
     sub         ax, ax             ;
      push       ax                        end
     mov         ax, dseg
     mov         ds, ax
     lea         dx, user_string
     mov         ax, 0aH
     int         21H;从键盘输
   入字符串
                                                           66
四、文件管理系统调用
     INT   21H 中与文件管理有关的调用有以下功能:
AH(功能号)功能         入口参数         出口参数
3C   建立文件       DS:DX=ASCII串地址 成功 AX= 文件代号
                CX=文件属性        失败 AX=错误码
3D       打开文件  DS:DX=ASCII串地址 成功 AX= 文件代号
               AL=0读,=1写,=2读写 失败 AX=错误码
3E       关闭文件   BX=文件代号         失败 AX=错误码
3F     读文件或设备 DS:DX=数据缓冲区地址 成功AX=实际读入字数
                 BX=文件代号           AX=0已到文件尾
                 CX=读取字节数       读错误:AX=错误码
40     写文件或设备 DS:DX=数据缓冲区地址 成功AX=实际写入字数
                 BX=文件代号
                 CX=写入字节数       写错误:AX=错误码
41      删除文件 DS:DX=ASCII串地址     成功 AX=00
                                错误AX=错误码 67
 例6:写数据文件,步骤:输入文件名,建立文件,写
文件,关闭文件。
Data segment            ;数据段
      mesgn       db    16 dup(?) ;文件名字符区
      datareg     db    2048dup(?) ;数据区
Data ends
Code segment            ;代码段
      …
      lea     si, mesgn ;文件名字符区的偏移地址
Rep1: mov ah,     1     ;键入文件名
      int     21h
      cmp al ,    0dh ;判断是否是回车键
     je   next1     ;是,转next1;否继续输入
                                        68
         mov    [si],   al    ;文件名字符送入
         inc    si
         jmp    rep1
Next1:   lea    dx,     mesgn ;文件名区的偏移地址
         mov    cx,     0     ;文件属性为0
         mov    ah,     3ch   ;建立文件
         int    21h
         mov    bx,     ax      ;文件代号送bx
         lea    dx,     datareg
         mov    cx,     2048    ;写入的字节数
         mov    ah,     40h     ;写文件
         int    21h
         mov    ah,     3eh   ;关闭文件
         int    21h
         …
Cseg     ends
         end
                                           69
 例7:读数据文件                                     int   21h
Mseg segment ; 数据段                            cmp   al, 2;2为文件未找到
   mesgn db 16dup(?)                          jne   ready;不等于2,转
   errmesg db       ‘File not found $’
                                              lea   dx, errmesg
   datareg db      2048dup(?)
                                              mov   ah, 9 ;显示字符串
Cseg      segment ;代码段
                                              int   21h
           …
     lea si, mesgn                            crlf  ;回车换行宏指令
beg: mov          ah, 1 ;键入文件名                jmp   beg
      int         21h                  Ready: lea   dx, datareg
      cmp         al, 0dh                     mov   cx, 2048
      je          next1                       mov bx, ax;ax中为文件代号
      mov         [si], al                    mov   ah, 3fh ;读文件
      inc         si                          int   21h
      jmp         beg                         mov   ah, 3eh ;关闭文件
Next1:lea         dx, mesgn
                                              int   21h
       mov        al, 0
       mov        ah, 3dh ;打开文件                                 70
  存取文件要借助于文件代号,文件代号是由打开文件功能
和建立文件功能时传送到AX中的。标准设备DOS已经定义
了它们的文件代号。
  文件代号:
  0 = 标准输入设备      1 = 标准输出设备
  2 = 标准错误输入设备
  3 = 标准辅助设备      4 = 标准打印设备
 我们建立或打开的文件,其代号从6开始顺序排列。任一
时刻,最多只能同时打开5个文件。
 错误返回代码:
01 非法功能号       02 文件未找到 03 路径未找到
04 同时打开文件太多 05 拒绝存取 06 非法文件代号
07 内存控制块被破坏 08 内存不够 09 非法存储块地址
                              71
五、单色显示器中断调用
 1、字符显示 显示器屏幕被划分成80列25行,行号为0-24,
列号为0-79;屏幕左上角为(0,0),右下角为(24,79),
共有2000个字符位置,每个字符在存储器中由两个字节表
示。一个是字符的ASCII码,另一个字节是字符的属性,属
性字节定义如下:

  闪烁位            亮度
 1=闪烁     背景颜色 0=正常亮度 前景颜色
          000=黑  1=加强亮度 000=黑
          111=白         111=白
 单色显示器存储容量为4KB,在内存中段址为B000H,
偏移地址为0000到0F9FH。
                                72
2、BIOS显示中断
  主要是INT 10H中断,功能号为0-0AH.
AH     功能        入口参数          出口参数
 0  置显示方式       AL=00-42h
 1  置光标类型 (CH)0-3=光标开始行
             (CL)0-3=光标结束行
2   置光标位置       BH=页号
              DH、DL=行、列号
3   读光标类型      BH=页号       CH=光标开始行
                           CL=光标结束行
                           DH=行号DL=列号
6   屏幕初始化或上卷
7   屏幕初始化或下卷
8   读光标位置的      BH=显示页       AH=属性
    属性和字符                    AL=字符
                                   73
 9   在光标位置显示 BH=显示页,AL=字符
     字符及属性      BL=属性
0AH 在光标位置只      BH=显示页
     显示字符       AL=字符CX=字符重复次数
例8:置光标开始行为5,结束行为7,光标在第5行第6列
    mov ch, 5
    mov cl, 7
    mov ah, 1 ;置光标类型
    int 10h
    mov dh, 5
    mov dl, 6
    mov bh,0
    mov ah, 2 ;置光标位置
    int 10h
                             74
例9:读光标的位置
    mov ah, 3
    mov bh, 0
    int 10h
 光标位置在DX中,DH行,DL列
例10:清屏
    mov ah, 7 ;屏幕下卷
    mov al, 0
    mov bh, 70H
;70H为白底黑字(反相显示),07H为黑低白字(正相显示)
    mov ch, 0
    mov cl, 0
    mov dh, 24
    mov dl, 79
    int 10H                 75
例11:字符显示,置光标到 0页的(20,25)位置,并以
正常属性(黑低白字)显示星号‘*’
    mov ah, 2 ;置光标位置
    mov bh, 0
    mov dh, 20;行
    mov dl, 25 ;列
    int 10H
    mov ah, 9 ;在光标位置显示字符及属性
    mov al, ‘*’
    mov bh, 0;页号
    mov bl, 7;黑低白字
    mov cx, 1;字符次数
    int 10H

                                76
3、DOS显示功能调用
     INT 21H
AH    功能                            参数
 2       显示一个字符                   DL=字符
    检验Ctrl+Break
 6 显示一个字符                          DL=字符
    不检验Ctrl+Break
  9 显示字符串                        DS:DX=串地址
例12:显示字符串
 MESSAGE DB ‘The sort operation is finished .’, 0DH,0AH, ’$’
     mov ah,9
     mov dx, seg MESSAGE
     mov ds, dx
      mov dx, offset MESSAGE
     int   21h
                                                       77
六、彩色图形显示
 1、显示方式
 ⑴ 文本方式 80×25或40×25
 16KB显示存储器作为显示缓冲区,能存储4页
(0-3)80×25字符或存储8页(0-7) 40×25。
 ⑵ 图形方式,把屏幕分成m × n点阵,每个点
是一个象素。
 ⑶ 设置显示方式,用 INT 10H调用,AH= 0(功
能号),调用参数在AL中。
 AL      功   能
  0    40 ×25 黑白文本方式
 1   40 ×25 彩色文本方式
                           78
AL            功     能
 2       80 ×25 黑白文本方式
 3       80 ×25 黑白彩色方式
 4       320 × 200彩色图形方式
 5        320 × 200黑白图形方式
 6       640 ×200黑白图形方式
 7       80 ×25 单色文本方式
 8       160 ×200 16色图形
 9       320 ×200 16色图形
 0A      640 ×200 16色图形
 0B-0C   保留给EGA
 0D      320 ×200 彩色图形(EGA)
 0E      640 ×200 彩色图形(EGA)
 0F      640 ×350 黑白图形(EGA)
 10H     640 ×350 彩色图形(EGA)
 11H     640 ×480 单色图形(EGA)   79
 AL            功       能
 12H       640 ×480 16色图形(EGA)
 13H       320 ×200 256色图形(EGA)
 40H       80 ×30彩色文本(CGA400)
 41H       80 ×50彩色文本(CGA400)
 42H       640 ×400彩色文本(CGA400)
2、文本方式
 每个字符占两个字节,一个是字符的ASCII码,另一个是
字符的属性,属性字节如下所示:
 闪烁位   背景   亮度位   前景
  RL   R   G   B   I   R   G   B
 RL为闪烁位,R、G、B为红绿蓝色,有背景R、G、B
和前景R、G、B,I为亮度位。
                                   80
IRGB   颜色
              例13:在品红背景下显示
0000    黑
               五个浅绿色闪烁的星号。
0001    蓝
0010    绿         mov ah,9
0011    青         mov al, ‘*’
0100    红         mov bh, 0
0101    品红
                  mov bl, 0DAH
0110    棕
                  mov cx, 05
0111    灰白
1000    灰         int  10H
1001    浅蓝    这里0DAH=11011010
1010    浅绿
1011    浅青
                闪烁   品红 浅绿
1100    浅红
1101    浅品红
1110    黄
1111    白                    81
 3、图形方式
   CGA显示器提供320×200(4色)和640 ×200(黑白)两
种图形分辨率。
 ⑴ 与图形 有关的BIOS调用
 INT 10H
AH=B 置背景色和彩色调色板 BH=1选择调色板,BH=0置
     背景色, BL 与BH配套使用,置调色板号或置背景色
AH=C 写象素       DX=行(0-199)CX=列(0-319或639)
               AL=象素值
AH=D 读象素        DX=行(0-199)CX=列(0-319或639)
                AL=象素值
 ⑵ 图形存储器映象
 320×200=64000个象素。存储器中每个字节表示四个象素,
每个象素用两位表示,可以有四种颜色,四种组合如下: 82
    象素值           调色板0        调色板1
     00            背景色        背景色
     01             绿           青
     10             红          品红
     11             棕           白
   背景色可以是16种颜色中一种。彩色存储器分成两部分,地址为B800:
0000到B800:1F3F对应于偶数行;B800:2000到B800:3F3F对应于奇数行。
 ⑶ 读写象素程序
 ① 置显示方式为图形方式
    mov ah, 0
    mov al, 04 ;CGA320×200图形方式
    int 10H
 ② 置背景色和调色板
    mov ah, 0bH ;置功能号
    mov bh, 00 ;置背景色
    mov bl, 05 ;背景色是05,品红色(0-F,16中颜色之一)
    int 10H                          83
   mov     ah, 0bH   ;置调色板
   mov     bh, 01    ;选择调色板
   mov     bl, 00    ;0号调色板
   int     10H
③ 在指定 坐标位置上读写象素点
   mov    ah, 0cH   ;写象素
   mov    al, color ;象素颜色
   mov    cx, colum ;列位置
   mov    dx, row   ;行位置
   int    10H
   mov    ah, 0dH   ;读象素
   mov    cx, colum ;列位置
   mov    dx, row   ;行位置
   int    10H       ;AL=象素值
                              84
 ⑷ 图形设计举例
例14:设置图形方式为320×200,背景色为兰色,调色
板0,每行显示一种颜色,每4行重复一次,直至整个屏
幕显示出彩条。
    code segment
         assume cs:code,ds:code,ss:code
    main proc far
         mov ah, 0
         mov al, 04 ;320 ×200图形方式
         int    10H
         mov ah, 0bH;置背景色
         mov bh, 0
         mov bl, 1 ;背景色为蓝色
         int    10H
                                      85
                                          cmp dx, 200;是200行?
       mov     ah, obH                    jne   line ;否,继续
       mov     bh, 1;选择调色板      bb:        mov  ah, 0
       mov     bl, 0;0号调色板                 int  16H
       int     10H                         cmp  ah, 1 ;1是ESC键
begin: mov     bl, 0;置初始色                             的扫描码
       mov     cx, 0;列号                    je   exit ;按ESC键退出
       mov     dx, 0;行号                    jmp  bb
line: mov      ah, 0cH;写点       exit:      mov  ah, 0
       mov     al, bl;置颜色                  mov al, 02;80×25黑白方
       int     10H                    式
        inc    cx;列加1                ret
      cmp      cx, 320;是320列?   main endp
       jne     line             code ends
       mov cx, 0;恢复列号为0              end  main
      inc      bl;改变颜色           第一条线为兰色(背景色)
       and     bl, 03            第二条线为绿色
       inc     dx ;行号加1          第三条线为红色
                                 第四条线为棕色                   86
                                 以后反复显示这四种颜色的线
 第五节 汇编语言程序设计
一、程序设计步骤
 1、分析问题
 2、确定算法
 3、画流程图,简单程序也可以不画流程图,
直接编程。
 4、内存空间分配
 5、编写程序
 6、静态检查
 7、上机调试
                    87
 二、循环与分支程序设计
  1、简单程序(顺序结构)
例1:用数据运算指令对两个16位数做加法,这两个数
从地址10050H开始连续存放,结果放在这两个数之后。
 分析题目,16位数相加,用加法指令,本题可以不考虑
进位问题。
 确定算法,8086加法指令可以做字操作,可以用不带进
位的加法指令;若用带进位加法指令,事前先清除进位。
 画流程图;
 内存空间分配:内存地址       内容
           10050H 被加数低8位
           10051H 被加数高8位
                            88
                          内存地址        内容
                          10052H     加数低8位
       开始                 10053H     加数高8位
                          10054H     和数低8位
      初始化                 10055H     和数低8位
                       编写程序:mov ax, 1000h;初始化
    被加数送AX                   mov ds, ax
                             mov si, 50h
                             mov di, 52h
                             mov bx, 54h
   被加数+加数送AX
                             clc         ;清除进位
                             xor ax, ax ;ax清零
AX送(10054H)和(10055H)
                            mov ax, [si]
                            adc ax, [di] ;带进位加法
       结束                   mov [bx], ax
                            hlt

                                            89
2、分支程序
分支程序的结构形式




            90
例2:符号函数
       1 当X>0
  Y=  0  当X=0 (-128≤X≤+127)
      -1 当X<0




                          91
 DATA      SEGMENT
 XX        DB         ?   ;X存储单元
 YY        DB         ?   ;Y存储单元
 DATA      ENDS
 extrn gnum:far,pnum:far
CODE       SEGMENT
      ASSUME CS:CODE, DS:DATA
 START: MOV AX,DATA
           MOV DS,AX
           call gnum      ;输入数据
           MOV XX,AX      ; XX  AX
           CMP AX,0       ;X与0比较
           JGE BIGPR      ;X≥0,转BIGPR
           MOV YY,0FF     ;X<0,(YY)     -1
           JMP EXIT       ;退出           92
BIGPR: JE EQUPR       ;X=0,转EQUPR
       MOV YY,1       ;X>0,(YY) 1
       JMP EXIT
EQUPR: MOV YY,0       ;X=0,(YY) 0
EXIT:  MOV AX,YY      ;将结果送AX
       CALL PNUM      ;屏幕上输出结果
       MOV AX,4C00H
       INT 21H
CODE   ENDS
       END START




                                    93
 例3:求AX和BX中两个无符号数之差的绝对值,结
果放在内存2800H单元。
         clc           ;清除进位
         sub ax, bx
         jc   aa       ;有借位转移
         mov di, 2800H ;ax大于等于bx
         mov [di], ax
         hlt
     aa: sub bx, ax
         mov di, 2800H ;ax小于bx
         mov [di], bx
         hlt                     94
例4:比较两个无符号数的大小,把大数存入MAX单元
解:比较两个无符号数,可采用CF标志位来判断大小,程序
 如下:
DATA    SEGMENT
SOURCE  DB    X1,X2         ;两个无符号数X1和X2
MAX     DB    ?             ;保留存放大数的单元
DATA    ENDS
CODE    SEGMENT
   ASSUME CS:CODE,DS:DATA
        MOV AX,DATA
        MOV DS,AX
        MOV AL,SOURCE       ;X1送AL
        CMP AL,SOURCE+1     ;X1-X2
        JNC   BRANCH        ;若X1>X2,转BRANCH
        MOV AL, SOURCE+1    ;否则,X2送AL
BRANCH: MOV MAX,AL          ;大数送MAX单元
CODE    ENDS
        END                             95
3、循环程序
⑴ 循环程序的结构形式
循环程序的结构有两种形式:
 DO_WHILE结构和DO_UNTIL结构。
① DO_WHILE结构
 对循环控制条件的判断放在循环的入口,先判断条件,
满足条件就执行循环体,否则就退出循环。
② DO_UNTIL结构
 先执行循环体,然后再判断控制条件,不满足条件则继
续执行循环操作,一旦满足条件就退出循环。
 如下图所示。
                          96
97
例5:求两个多字节数之和,这两个数在10050H单元开始的内
存中连续存放,结果存放在两数之后,设这两个数均为8个字
节长。
Start: mov ax, 1000H aa: mov   ax, [si]
       mov ds, ax        adc   ax, [di]
       mov si, 50H       mov   [bx], ax
       mov di, 58H       pushf
       mov bx, 60H       add   si, 2
       mov cx, 4         add   di,2
       clc               add   bx,2
                         popf
                         loop  aa
                          hlt
;第一个数在10050H-10057H,第二个数在10058H-1005FH
;结果数据在10060H-10067H
                                    98
例6:用循环结构编写程序完成
              10
      SUM    a
              i 1
                     i    a1  a2      a10


 DATA    SEGMENT
 BUFFER DW a1,a2,…,a10 ;原始10个数据
 SUM     DW ?          ;存放和数
 DATA    ENDS
 CODE    SEGMENT
    ASSUME CS:CODE,DS:DATA
 START:MOV AX,DATA
        MOV DS,AX
        MOV AX,0       ;AX中为和数,初始为0
        MOV DI,OFFSET SUM ;存放结果地址送DI
        MOV BX,OFFSET BUFFER
                       ;数据缓冲区首地址送BX
                                                   99
       MOV     CX,10    ;循环次数送CX
LOP:   ADD     AX,[BX]  ;累加
       INC     BX       ;修改数据缓冲区地址
       INC     BX
       DEC     CX       ;次数减1
       JNZ     LOP ;到10次了吗?不到,转LOP
       MOV     [DI],AX  ;到10次,存放结果和
       MOV     AX,4C00H ;返回DOS
       INT     21H
CODE    ENDS
        END    START



                                 100
三、子程序
 ⑴ 子程序调用和返回
  子程序调用:CALL 子程序名
   有近程调用和远程调用;直接调用和间接调用
  子程序返回:RET或RET n
  CALL时自动保护返回地址,RET指令把压入堆栈的
返回地址弹出送IP或CS:IP(段间返回)。
⑵ 现场的保护与恢复
⑶ 参数的传递,指主程序和子程序之间相关信息或数据
的传递。传递方式有寄存器、内存单元或堆栈三种。
⑷ 子程序的嵌套和递归调用
  子程序调用子程序的过程称为嵌套调用;子程序调用
自身的过程称为递归调用。
                          101
例7:延迟1秒的子程序
   DELCH proc far
   delay: pushf
          push bx
          push cx
          mov bx, 03e8H ;外循环次数1000
   lp2: mov cx, 176H ;内循环次数176H
   lp1: pushf
          popf
          loop lp1      ;内循环
          dec   bx
          jnz   lp2     ;外循环
          pop   cx
          pop   bx
          popf
          ret                        102
例8:利用递归调用编制             assume cs:code,ds:data,ss:stack_seg
                        Start:mov ax,stack_seg
计算N!(N≧0)的程序
;计算N!结果在result中               mov ss, ax
;数据段                         mov sp, offset tos
Data    segment              push ds     ;保存原数据段
   N         dw     ?        sub ax, ax
   Result    dw     ?        push ax
Data    ends                 mov ax, data
;堆栈段                         mov ds, ax
Stack_seg segment       ;程序主要部分
   dw 128 dup(?)
                             mov bx, N
   tos label word ;栈顶
                             push bx ;保存N
Stack_seg ends
                             call fact ;调用求N!子程序
;码段
Code segment                 pop result ;结果在result中
Main proc       far          ret
                        Main endp
                                                      103
;子程序,求N!
                             exit: mov [bp+6], ax
fact proc near
                                   pop bp
     push ax    ;
     push bp                       pop ax
     mov bp, sp                    ret
     mov ax, [bp+6];取N       fact     endp
     cmp ax, 0               code ends
     jne  fact1;ax不等于                 end  start
            零,转fact1
       inc  ax ;ax=0, 0!=1   注:利用堆栈传递参数,其递
       jmp exit
                             归定义如下:
fact1: dec  ax ;ax=N-1
                               0 !=1         (N=0)
       push ax
       call fact ;递归调用         N !=N*(N-1) !(N﹥0)
       pop  ax
                                                    104
       mul [bp+6]
计算N!时进堆栈示意图(设N=3):           出栈示意图:

          BP                       BP    第3次递归
        AX=0                      AX=0    调用出栈
        返回地址    第3次递归调用          返回地址
        AX=0                     AX=1   1送[BP+6]
         BP                        BP    第2次递归
         AX=1                    AX=1     调用出栈
        返回地址    第2次递归调用          返回地址
         AX=1                    AX=1*1
         BP                        BP    第1次递归
         AX=2                    AX=2     调用出栈
        返回地址    第1次递归调用          返回地址
         AX=2                    AX=1*2
         BP                        BP    主程序调用
        DATA                       AX    fact返回
        返回地址    主程序调用fact        返回主程
         N=3                      2*3=6  result
         AX=0                     AX=0
          DS                       DS         105
   SP                       SP
四、程序 设计举例
 1、程序模块的定义与通讯
   汇编语言可以把程序分成许多模块,每个模块独立地进行
汇编和调试。所有模块汇编后,由连接程序装配成一个完整的
可执行程序。在模块之间有通讯能力,以共享数据和程序代码。
   ① 程序模块的定义
  NAME     < 模块名 >
       语句
  END      < 标号 >
  其中NAME语句可以省略,则该模块的源程序名(文件名)
就是模块名;END后面的标号可省略,若是主模块,此标号是
程序的启动地址。
   ② PUBLIC和EXTRN,用这两条伪指令说明通信用的变量、
标号、过程等(具体用法参见伪指令一节)。
  注意:EXTRN在有些版本中可写成EXTERN      106
                             ;源模块2
  例9:模块化程序设计
;源模块1                      extrn var1:byte,var4:word
                           public var2
extrn var2:word,lab2:far
                           data2 segment
public var1,lab1,var4
                              var2      dw  0
data1 segment
   var1      db   ?           var3      db  5dup(?)
                           data2 ends
   var3      dw   ?
   var4      dw   ?        ;源模块3
data1 ends                 extern lab1:far
code1 segment              public lab2,lab3
                                   …
        …
                           lab2 proc far
lab1: …
                                   …
        …
                           lab2 endp
code1 ends
                           lab3: …
        …                                       107
                                   …
2、程序设计举例
 ⑴ 起泡算法排序(从大到小或从小到大)
  从第一个数开始依次对相邻两个数比较,若次序对,
则不做任何操作,如次序不对,则使这两个数交换位置。
这种算法称为起泡算法。
 序号     数  第一遍 第二遍 第三遍 第四遍
   1     8   8  16  32  84
   2     5   16 32  84  32
   3    16   32 84  16  16
  4     32   84  8   8   8
  5     84    5  5  5    5
 第一遍进行4(N-1次)比较,最小数已到最下面,第二
遍进行3(N-2次)比较,依次类推,最多进行N-1遍即可
完成排序。                      108
例10:首地址为ARER的数              Loop2: mov    ax, area[bx]
                                   cmp    ax, area[bx+2]
组,从大到小排序。                          jge    continue;大于
Data    segment
                                         等于,转continue
   area dw n dup(?) ;n个数
                                   xchg   ax, area[bx+2]
Data    ends
                                   mov    area[bx], ax
Code segment
                            Continue:add  bx, 2
Main proc       far
                                    loop  loop2
   assume cs:code,ds:data
                                    mov   cx, di
Start: push     ds
                                    loop  loop1
        sub     ax, ax
                                    ret
        push    ax
                            Main endp
        mov     ax, data
                            Code ends
        mov     ds, ax
                                    end  start
        mov     cx, n
                            注:loop2循环是每遍中的比较
        dec     cx
                                loop1循环是比较多少遍
Loop1: mov      di, cx
        mov     bx, 0
                                                       109
(2)折半查找算法
   在一个按顺序排列的数组中要查找某个数,通常采用折
半查找法。方法是先取有序数组的中间元素与查找值比较,
如相等,则查找成功;如查找值大于中间元素,则取高半部
的中间元素与查找值比较;如如查找值小于中间元素,则取
低半部的中间元素与查找值比较;如此重复直到查找成功或
查找不成功为止。
   折半查找算法的效率高于顺序查找法,对于长度为N的
表格,顺序查找法平均要作N/2次比较,而折半查找法平均比
较次数约为log2 N。所以,如果数组长度为100,则顺序查找
法平均要作50次比较,而折半查找算法平均只要作7次比较就
可以了。
例11:数组12,11,22,33,44,55,66,77,88,99,111,
222,333;要求查找55。                     110
数组长度为12。
 第一次与第6个元素66比较;55<66,所以第二次到低
半部去进行折半查找,比较的是第3个元素33;因55>33,
所以第三次到高半部去折半查找,比较的是第5个元素55;
这样经过3次查找,就可以完成查找过程。查找中也有可
能没有找到而查找失败。
 具体程序可参阅有关汇编程序的书。
例12 在附加段中,有一个按从小到大顺序排列的无符号
数数组,其首地址存放在DI寄存器中,数组中第一个单
元存放着数组长度,第二个单元存放着数组的实际长度。
在AX中有一个无符号数,要求在数组中查找(AX),如
找到,则使CF=0,并在SI中给出该元素在数组中的偏移
地址;如未找到,则使CF=1。
                           111
  一个长度为n的有序数组r,查找元素k的折半查找法可
描述如下:
(1)初始化被查找数组的首尾下标,low = 1,high = n;
(2)若low > high,则查找失败,置CF=1,退出程序。
  否则,计算中点:mid = (low+high)/2;
(3)k与中点元素r[mid]比较。若k= r[mid],则查找成功,
程序结束;若k< r[mid],则转步骤(4);若k> r[mid],
则转步骤(5);
(4)低半部分查找(lower),high = mid-1,返回步骤(2)
继续查找;
(5)高半部分查找(higher),low = mid+1,返回步骤(2)
继续查找。

                                  112
113
dseg   segment
       low_idx      dw    ?
       high_idx     dw    ?
dseg ends
cseg segment
       b_search     proc near
       assume cs:cseg,ds:dseg,es:dseg
start: push ds
       push ax
       mov ax,dseg
       mov ds,ax
       mov es,ax
       pop    ax
                                        114
      ;ax与第一个和最后一个元素比较
      cmp ax,es:[di+2];与第一个元素比较
      ja    chk_last ;大于,转chk_last
      lea si,es:[di+2]
      je    exit     ;等于,转
      stc            ;小于,CF=1
      jmp exit
chk_last:mov si,es:[di];取数组长度
      shl si,1       ;数组长度乘2
      add si,di      ;si指向最后一个元素
      cmp ax,es:[si];与最后一个元素比较
                                     115
     jb     search ;ax小于最后一个元素,转search
     je     exit   ;AX=最后一个元素,转exit
     stc           ;ax大于最后一个素,
                   ;查找不成功,CF=1
      jmp   exit
search:
      mov   low_idx,1
      mov   bx,es:[di]
      mov   high_idx,bx
      mov   bx,di     ;数组首地址


                                    116
mid: mov cx,low_idx
     mov dx,high_idx
     cmp cx,dx
     ja  no_match ;大于,查找不成功,转
     add cx,dx       ;小于等于,
                       ;求中间元素下标
     shr cx,1        ;除于2
     mov si,cx
     shl si,1;si中为数组中间元素的偏移地址
compare:
     cmp ax,es:[bx+si]
                             117
        je    exit          ;等于,转
        ja    higher        ;大于,转
        dec   cx            ;小于,改变高下标
        mov   high_idx,cx
        jmp   mid
higher: inc   cx            ;大于,改变低下标
        mov   low_idx,cx
        jmp   mid
no_match:
        stc                 ;查找不成功,CF=1
exit: pop     ds
        ret
b_search      endp
cseg ends
        end   start                       118
 第六节 汇编语言程序的上机过程
一、进入系统
1、Ctrl+Alt+Del 系统热启动
 屏幕显示:如果不是授权用户,请勿登录
          确定
 2、按确定后,屏幕显示:
   用户名:WL35-6
   密码: cjx61504
   域:student6(4号机房、5号机房)
   欢迎使用Windows NT!         119
 3、出现Windows界面
  光标指向左下角,点击开始,出现一菜单,选择其中
程序项,点击,出现子菜单。
   在子菜单中选择“MS-DOS命令提示符”,点击后系
  统
退回到DOS方式,出现DOS提示符Z : \ >
 4、 Z : \ > E : (回车);Z是系统盘,用户不能用。
   要进入E盘(这是用户盘)
 5、E : \ > MD ASM (回车);建立ASM子目录
 6、E : \ > CD ASM (回车);进入ASM子目录
 7、E : \ ASM > copy Z : \WL51-2\masm\*.* (回车)
 8、E : \ ASM > DIR (回车)
   在ASM子目录下有5个程序;用户编写的程序也可放
                                              120
在该目录下。
  A .OBJ    ;老师提供的模块
  DEBUG.EXE ;调试程序
  EDIT.COM  ;文本编辑程序
  MASM.EXE ;宏汇编程序
  LINK.EXE  ;链接程序

  其中A.OBJ 是老师提供的目标程序模块
  A.OBJ中提供了两个子程序:gnum和pnum,在程序
中可以直接调用,即CALL gnum;CALL pnum
  gnum程序是从键盘上得到一个有符号的十进制数,
并转换为二进制数装入寄存器AX中。
  pnum程序是在屏幕上用十进制数显示AX中的有符号
的二进制数。
  在用户程序中前面要加extrn gnum:far,pnum:far
                                      121
二、编辑程序
  编辑程序分为行编辑程序和全屏幕编辑程序。
在DOS操作系统中提供行编辑程序EDLIN.EXE
和全屏幕编辑程序EDIT. COM。此外还有其他编
辑程序也可以,如WORDSTAR,WPS等。还有
一些集成开发环境IDE中也有全屏幕编辑功能。
 用编辑程序编写汇编语言源程序,文件名可以
自己选择,但扩展名必须是.ASM。
三、汇编程序
 宏汇编程序MASM.EXE将汇编语言源程序汇编
                        122
成二进制形式的目标程序。经汇编后可以建立三个
文件,一个是扩展名为OBJ的目标文件,一个是扩展
名为LST的列表文件,另一个是扩展名为CRF的交叉
索引文件。
 对源程序汇编后,若有错误,屏幕上会显示出错误
内容。然后,在编辑程序中去修改错误,直至汇编后
没有错误为止。
四、连接程序(LINK.EXE)
 目标程序必须经过连接后,才能成为扩展名为.EXE
的可执行文件。
 多个目标文件连接时,可用空格或+号来连接。
例如:LINK 文件名.obj A.obj(回车)
   这里.OBJ可以省略             123
  *第七节 高级汇编语言技术
一、条件汇编
 宏汇编提供了一组条件伪指令,其格式是:
 IF×× 表达式
     … 语句序列1
  ELSEIF×× 表达式
     … 语句序列2
  ELSE
     … 语句序列3
  ENDIF
 当表达式条件为真时,汇编程序就汇编语句序列1;若
表达式条件为假时,汇编程序就汇编语句序列2。
 具体的条件汇编伪指令如下:
                        124
1、IF   表达式   ;若表达式的值不为0,则条件为真。
2、IFE 表达式    ;若表达式的值为0,则条件为真。
3、IF1 ;若汇编程序处于对源程序的第一遍扫描过程中,
        则条件为真。
4、IF2 ;若汇编程序处于对源程序的第二遍扫描过程中,
        则条件为真。
5、IFDEF 符号 ;若指定的符号已被定义或已由EXTRN
  伪指令作外部说明,则条件为真。
6、IFNDEF 符号 ;若指定的符号未被定义且没经EXTRN
  伪指令作外部说明,则条件为真。
7、IFB 参数;若参数是空,则条件为真。
8、IFNB 参数;若参数不是空,则条件为真。
9、IFIDN 参数1,参数2;若两个参数是相同的串,则条件为真。
10、IFDIF 参数1,参数2;若两个参数是不同的串,则条件为真。
                               125
例1:定义一个宏以实现寄存器移位操作。移位的方向由参
数dir决定;移位的次数由参数count决定,且移位次数为常数。
 当移位次数为0时,无需移位,宏展开为空;
 当移位次数为1时,移位次数在移位指令中由立即数给出 ;
 当移位次数大于1时,移位次数通过CL寄存器给出。
  shift MACRO reg , dir , count
        IFE   count
              EXITM        ;移位次数为0,退出
        ELSEIF count GT 1
              mov cl, count
              sa&dir reg, cl ;算术移位次数大于1
        ELSE
              sa&dir reg, 1 ;移位次数为1
        ENDIF
        ENDM                          126
二、重复汇编
1、计数重复汇编伪指令REPEAT/REPT
 格式:REPEAT 数值表达式
            语句序列
       ENDM
 该伪指令是汇编程序对给定的语句序列连续重复汇编
由表达式给出的次数。
 例2:在内存81个连续单元中定义一个九九乘法表
    m=0
    table99    EQU THIS byte



                           127
   REPEAT 9
     m=m+1
     n=0
         REPEAT 9
           n=n+1
           db n*m
         ENDM
   ENDM
2、条件重复汇编伪指令WHILE
 格式:WHILE 数值表达式
         语句序列
     ENDM
 该伪指令是汇编程序对给定的语句序列重复汇编,直到
数值表达式的计算结果为假(零)。        128
3、给定参数的重复汇编FOR/IRP
 格式:
FOR 形参, <实参[,实参]…>
   语句序列
ENDM
FOR伪指令使汇编程序对给定的语句序列连续重复汇编
若干次,每次重复汇编时均依次从实参表中取出一个实
参替换形参,直到所有实参均已用过时停止汇编。汇编
次数有实参个数决定。
例3:FOR I , <1,2,3,4,5>
      db 10*I
    ENDM

                        129
展开后的语句序列为:
  db 10*1
  db 10*2
  db 10*3
  db 10*4
  db 10*5
4、给定字符的重复汇编FORC/IRPC
  格式:FORC 形参,字符串
          语句序列
       ENDM
 该伪指令与FOR伪指令类似,不同之处在于前者实参
是字符串中的单个字符。


                            130
三、汇编语言与高级语言混合编程
1、汇编语言与高级语言的比较
     特  征       汇编语言 高级语言
   目标程序运行时间       短    长
 程序运行时占用的存储空间     小    大
  直接访问硬件的能力       强    弱
  程序可读性和可维护性     差    强
  程序开发的复杂程序      高    低
   程序的可移植性       差    强
 对硬件环境和软件的兼容性    差    强
                      131
 2、嵌入式汇编
 许多高级语言中包含了嵌入式汇编程序,允许在高级
语言源程序内插入用汇编写的个别语句序列。
⑴ Mcrosoft C 6.0 允许在C语言程序中使用_asm修饰符嵌
入汇编语言语句,其格式为:
 ① 单行汇编格式:
     _asm 汇编语言指令
这里, _asm 指明本行内的后续部分为嵌入式汇编语言
语句。
 ② 多行汇编格式: _asm
                  ﹛
                    汇编语言语句序列
                  ﹜
 这里, _asm 指明其后大括号内的每一行都是嵌入式汇
编语言语句。
                                  132
⑵ Borland公司的Turbo C提供了直接插入汇编指令,其
格式为:
   asm 汇编指令;或换行符
 例4:
      if ( i>0)
          _asm mov al, 4
      else
         i=7;
四、库的使用
 库文件可分为两种,一种是文本形式的汇编语言源
程序库,另一种是二进制形式的目标文件库。
 对库文件的引用使用以下伪指令:
  INCLUDE 库文件名;引用汇编语言源程序库
  INCLUDELIB 库文件名;引用二进制形式的目标文件库
                                   133
五、汇编语言与C语言的接口
 在汇编语言与高级语言混合编程时,必须遵从一系列的接口约定。
1、符号的定义和说明
 ⑴ 字母的大小写:对字母的大小写C语言敏感,而汇编语言不敏感。
许多汇编程序的版本允许使用汇编开关通知汇编程序以大小写敏感
的方式进行汇编。
⑵ 下划线
 C语言产生的目标文件自动为每个标设符加入下划线作为前缀,所
以汇编程序中引用C语言的符号时,应在符号名前增加一个下划线;
而在汇编中定义需要由C语言引用的符号时,也要在符号名前加一个
下划线。
 例5:在C语言中定义如下变量
     int number
则在汇编程序中需要用带有下划线的该变量:
    mov  [ _number],ax
                              134
 2、全局符号的定义和说明
  ⑴ C语言中符号的定义和说明:按C语言版本的规定。
  ⑵ 汇编语言中符号的定义和说明
  如果C语言中使用汇编中的变量或函数,则在汇编中使
用PUBLIC或EXTERNDEF伪指令说明变量名或函数名具
有公共属性。
  反之,在汇编中使用C中定义的变量或函数,应在汇编
中使用EXTERNDEF或EXTERN伪指令说明。
3、段和内存模式
  由于高级语言中段是由编译程序自动生成段结构,因
此在混合编程中,应使汇编程序遵从高级语言的段约定。
4、寄存器的约定
  在混合编程中,CPU内部的寄存器也是公共资源。所
以在互相调用时,要注意如何使用和保护CPU寄存器的
问题。                           135
5、参数的传递
C语言中执行函数调用时,函数的入口参数是通过堆栈传递的。
⑴ 参数类型:C语言参数类型见下表:
        原始类型                   入栈类型           在堆栈中占用的字节数
           char                   int              2
     unisigned char          unisigned int         2
        short , int               int              2
unisigned short, unisigned   unisigned int         2
          long                   long              4
     unisigned long          unisigned long        4
          float               float/double        4/8
         double                 double             8
     near(指针)                near(指针)              2
      far(指针)                 far(指针)              4
          array              数组起始地址               2或4
          struct               按值入栈
          union                按值入栈
                                                        136
 80x86的堆栈是以字为的单位的,故相应的类型入栈时都要
转换成相应的16位数据类型。
⑵ 函数的调用规则
 C语言的压栈顺序是按从右到左的顺序将参数依次入栈。对
调用者和被调用者而言,当调用返回时,由压入参数的调用
者负责将堆栈中的参数废弃。
例6:在C语言中定义如下的函数:
     void test (char a, int *b, char c[ ])
 并给出下面调用:
     test ( i , &j, k);
 在本例中,数组k作为参数传递时,将k的起始地址的近指
针两个字节入栈;指针j的缺省属性也是near,也占两个字节入
栈;字符型参数 i 扩展成两个字节入栈。故在调用返回时,
由调用代码执行一条加法指令将堆栈指针加6,以便从堆栈中
清除入栈时占用的堆栈空间。
                                       137
 6、返回值传递规则
  在C语言和汇编语言相互调用过程中,还需考虑函数返回值的传递。
C语言的函数返回值是通过约定寄存器传递的。如下表所示:
    返回值类型                       返回值的位置
char, unsigned char                AL
short, unsigned short              AX
  int, unsigned int                AX
long, unsigned long               DX:AX
   float, double        静态存储器(AX或DX:AX指向存储器起始地址)
指针             near                AX
                far               DX:AX
结构          1字节                     AL
和联合         2字节                     AX
            4字节                   DX:AX
                                             138
          4字节以上         静态存储器(AX或DX:AX指向存储器起始地址)
六、在C语言中调用汇编程序
 在C语言中调用汇编语言中定义的数据或过程,必须让汇编语言遵从
C语言的接口约定。
例7:许多应用程序用C语言实现人机接口,而实际的数据处理通过汇
编子程序完成。本例要求在C语言主程序中从键盘上输入一个两位十进
制数,再调用汇编子程序将该数的个位与十位数互换,并返回到C语言
主程序中打印结果。
  C语言主程序:
    #include “stdio.h”
     extern unsigned char reverse (unsigned char byte);
    main( )
    ﹛
              unsigned char byte;
              printf(“\nEnter an unsigned byte: ”);
             scanf(“%d, &byte);
             printf(“The reversed byte is: %d \ n ”, reverse(byte));
    ﹜
 汇编语言程序如下:                                                           139
 .model small       ;模式定义,定义为小模式
 .code              ;简化码段定义
 public _reverse
 _reverse proc
 ; C callable function to reverse a byte
 ; C prototype:extern unsigned char reverse(unsigned char byte);
push       bp
 mov       bp, sp
 mov       bh, 10
 mov       al, [bp+4]        ;取参数时应从栈顶退回4个字节
xor        ah, ah
 div       bh                ;用10整除该数,分离出个位和十位
 mov       bl, al
 mov       al, ah
 mul       bh
 add       al, bl           ;两者交换后,再重新组合成两位十进制数
 pop       bp
 ret
 _reverse endp                                                   140
 end
七、在汇编语言中调用C语言程序
 混合编程多数都是C语言中调用汇编,只有在特殊情况
下从汇编语言中调用C语言的函数或数据。
例8:本例以C语言模块作为启动主模块,其中有调用汇
编语言函数的语句。而在汇编语言函数中,通过DOS调
用,取得当前的系统时间,并调用C语言的标准格式输出
函数printf打印当前的时间。
  C语言模块“
     #include <stdio.h>
     extern void get_date(void);
     main( )
     ﹛
             get_date( );
      ﹜
   汇编语言模块:
                               141
.model small          ;简化段定义,定义为小模式
extern       _printf:proc
public       _get_date
.data                 ;简化数据段定义
format        db 0ah,”Today is %u/%u/%u.”,0ah,0dh ;输出格式
.code                 ;简化代码段定义
_get_data proc
   mov       ah, 2ah ;取系统时间,年、月、日
   int       21h      ;分别在cx、dh、dl总
   xor       bh, bh
   mov       bl, dl   ;DL中返回“日”
   push      bx
   mov       bl, dh ;DH中返回“月”
   push      bx
   mov       bx, cx ;CX中返回“年”
   push      bx
   mov       bx, offset format ;输出格式

                                                          142
   push        bx
   call        _printf
  ;printf (“\nToday is %u/%u/%u.\n”,year,month,day)
  add          sp, 8
  ret
  _get_date endp
  end
八、简化段定义
在高版本的宏汇编语言中增加了许多新的伪指令,下面只介绍有关模块定义
和段定义伪指令。

1、模块定义伪指令(.MODEL)
格式:
.MODEL 存储模式 [,语言类型][,操作系统类型][,堆栈类型]
(1)存储模式
 微模式tiny、小模式small、中模式medium、紧缩模式compact、

                                                      143
大模式large、巨模式huge
各种存储模式的比较见下表:
存储模式      代码段   数据段    容量限制      代码的距    数据的距
                                 离属性     离属性
 tiny     代码和数据组合成一个   代码+数据      Near    near
               段        ≤64K
 small     1     1     代码≤ 64K    near    near
                       数据≤ 64K
medium     多个    1     数据≤ 64K    far     near

compact    1    多个     代码≤ 64K    near    far

 large     多个   多个       无        far     far

 huge      多个   多个       无        far     far
                                          144
(2)语言类型,指明语言类型,如C、BASIC、PASCAL
、FORTRAN等。
(3)操作系统类型,指明生成的目标代码在哪一种操作系
统上运行。
(4)堆栈类型,该参数指定堆栈段和数据段是否组合在同
一物理段中。
2、段的定义
 一旦使用了.MODEL伪指令定义了存储模式,就可以使用
简化段定义伪指令定义段。
  代码段由. CODE伪指令定义,其格式为:
  . CODE [段名];段名为 _TEXT或模块名_TEXT
  堆栈段由.STACK伪指令定义,其格式为:
  . STACK [大小];缺省为1024字节,段名STACK

                              145
数据段定义有三条伪指令:
 (1).DATA:定义初始化的数据段
    格式: .DATA
    段名:_DATA
 (2) .DATA?:定义未初始化的数据段
    格式: .DATA?
    段名:_BSS
  (3).CONST:定义常数数据段
    格式: .CONST
    段名: CONST
例8:使用简化段定义,调用DOS的字符串显示输出中断
向屏幕输出一个字符串。
                         146
.MODEL       small    ;定义存储模式:小模式
.STACK       256      ;定义堆栈段,256字节
.DATA                 ;定义数据段
msg    db    “This program illustrates the usage of”
       db    “complete segment control directives.”
       db    0dh,0ah
       db    “$”
.CODE                 ;定义代码段
       mov   ax,@data
       mov   ds,ax
       mov   ah,09h
       mov   dx, offset msg
       int   21h
       mov   ax,4c00h
       int   21h
       END

                                                       147
附录:有符号十进制数转换为二进制数子程序F10T2
 n位有符号十进制数(包括符号位)有三种形式:
 d1d2 d3 …dn-1 ,+ d1d2 d3 …dn-1 ,- d1d2 d3 …dn-1
  其中d(i=1,2, …,n-1)为0-9中的一个数字。
  在十到二进制数转换时,首先记下其符号,然后将数
字部分d1d2 d3 …dn-1转换成二进制数 。 d1d2 d3 …dn-1可用
如下公式表示:
 ( …( d1*10+ d2)*10+ …)*10+ dn-1
 上面计算得到的是该数的绝对值。如果符号为负,则将
绝对值求补,得到所求二进制数的补码表示,否则绝对
值就是该数的二进制数补码表示。
 设十进制数的符号及其数字的ASCII码按高位在前,低
位在后的顺序放在由SI指示的字节存储器中,十进制数的
位数放在CX中,转换后的结果在AX中,则子程序F10T2
可编程如下:                                           148
data  segment
      dat   dw    10
      sign db     ?
data ends
f10t2 proc far
      push dx
      push bx
      mov ax,0
      mov sign,0
      mov bl,[si]     ;取一ASCII码送BL
      cmp bl,’+’
      jz    A     ;若为正号,转A
      cmp bl,’-’
      jnz   next2 ;不是正号、负号,转next2
      mov sign,1      ;为负号,1送sign
      dec   cx    ;ASCII码个数减1        149
next1:   inc   si
         mov   bl,[si]
next2:   cmp   bl,30h
         jb    D1        ;若不是0-9的ASCII码,即
         cmp   bl,39h ; 为非法数,转D1
         ja    D1
         sub   bl,30h;是字符0-9,将其真值0-9送bx
         mov   bh,0
         mul   dat    ;(ax)*10送dx,ax
         jo    D1     ;若溢出,转D1
         add   ax,bx ;(ax)+(bx)送ax,
         jc    D1     ;若有进位,表示溢出,转D1
         dec   cx
         jne   next1 ;cx不为0,转next1,继续转换
                                      150
      cmp sign,1
      jne QQ     ;当被转换数为负数时,(AX)求补
      neg ax
 QQ:pop bx
      pop dx
      ret
 D1: mov si,-1 ;-1送si表示为非法十进制数或溢出
      jmp QQ
 F10T2    ENDP
对子程序f10t2的调用如下:
 stack    segment
      db  200 dup(0)
 stack    ends


                                151
data       segment
bufo       db ‘PLEASE INPUT DATA:’,0DH,0AH,’$’
buf1       db     7
           db     ?
           db     7 dup(0)
buffer     db     0dh,0ah,’ERROR!’,’$’
data       ends
code       segment
     assume cs:code,ds:data,ss:stack
begin:     mov ax,data
           mov ds,ax
           mov ax,stack
           mov ss,ax

                                             152
lea    dx,bufo
mov    ah,9
int    21h
lea    dx,buf1
mov    ah,0ah
int    21h
lea    si,buf1+2;待转换数的存储器首地址送SI
mov    cl,[si-1] ;待转换数的位数送CX
mov    ch,0
call   f10t2
cmp    si,-1;若为未溢出的合法十进制数,转exit
jne    exit
lea    dx,buffer;溢出或非法的十进制数,显示出错
                             153
     mov   ah,9
     int   21h
exit:mov   ax,4c00h
     int   21h
data       segment
dat        dw     10
sign       db     ?
data       ends
f10t2      proc far     ;子程序F10T2
           …
f10t2      endp
code       ends
           end    begin

                                    154

								
To top