汇编网首页登录博客注册
goodboy的学习博客
博客首页博客互动【做检测题】论坛求助

我的博客

个人首页 |  我的文章 |  我的相册 |  我的好友 |  最新访客 |  文章收藏 |  论坛提问 |  友情链接 |  给我留言  
图片载入中
学习动态
好友圈
文章收藏
友情链接

[2008-11-24 11:55] 80*86伪指令

word版下载
http://assembly.ys168.com
8086伪指令(汇编语言程序格式)

汇编语言程序中的语句可以由指令、伪指令和宏指令组成。上一章我们介绍了8086指令系统中的6类指令,每一条指令都对应一种CPU操作。

伪指令又称为伪操作,它是在对源程序汇编期间由汇编程序处理的操作,它们可以完成如处理器选择、定义程序模式、定义数据、分配存储区、指示程序结束等功能。

宏指令是由用户按照宏定义格式编写的一段程序,其中语句可以是指令、伪指令,甚至是已定义的宏指令。宏指令将在第七章中介绍。

伪指令和指令的区别在于,每一条指令必须生成机器代码,然后在程序运行期间由CPU来执行其操作;而伪指令是在汇编期间由汇编程序执行的操作命令,除了数据定义及存储器分配伪指令分配存储器空间外,其它伪指令不生成目标码。和各种指令一样,伪指令也是程序设计不可缺少的工具。下面介绍一些常用的伪指令。 

4.2.1 段定义伪指令
 
段定义伪指令是表示一个段开始和结束的命令,80x86有两种段定义的方式:完整段定义和简化段定义,分别使用不同的段定义伪指令来表示各种段。

4.2.1.1 完整的段定义伪指令

完整段定义伪指令的格式如下:

段名 SEGMENT
  .
  .
  .
段名 ENDS 

段名由用户命名。对于数据段、附加段和堆栈段来说,段内一般是存储单元的定义、分配等伪指令语句;对于代码段中则主要是指令及伪指令语句。

定义了段还必须说明哪个段是代码段,哪个段是数据段。ASSUME伪指令就是建立段和段寄存器关系的伪指令,其格式为:

ASSUME 段寄存器名: 段名,…

段寄存器名必须是CS、DS、ES和SS中的一个,而段名必须是由SEGMENT定义的段名。

·定位类型:说明段的起始边界值(物理地址)。
  
·组合类型:说明程序连接时的段组合方法。

·类别:在单引号中给出连接时组成段组的类型名。连接程序可把相同类别的段的位置靠在一起。

例4.1

  ; * * * * * * * * * * * * * * * * * * * * * * *
  data_seg1 segment        ; 定义数据段
            .
            .
            .
  data_seg1 ends
  ; * * * * * * * * * * * * * * * * * * * * * * * 
  data_seg2 segment        ; 定义附加段
            .
            .
            .
  data_seg2 ends
  ; * * * * * * * * * * * * * * * * * * * * * * *
  code_seg segment         ; 定义代码段

   assume cs:code_seg, ds:data_seg1, es:data_seg2

  start:              ; 程序执行的起始地址
  ; set DS register to current data segment
     mov   ax, data_seg1   ; 数据段地址 
     mov   ds, ax       ; 存入DS寄存器

  ; set ES register to current extra segment
     mov   ax, data_seg2   ; 附加段地址
     mov   es, ax       ; 存入ES寄存器
            .
            .
            .
   code_seg ends          ; 代码段结束
  ; * * * * * * * * * * * * * * * * * * * * * * * * * *
     end   start

  由于ASSUME伪指令只是指定某个段分配给哪一个段寄存器,它并不能把段地址装入段寄存器中,所以在代码段中,还必须把段地址装入相应的段寄存器中:

  MOV    AX,DATA_SEG1    ; 数据段地址
  MOV    DS,AX        ; 存入DS寄存器
  MOV    AX,DATA_SEG2    ; 附加段地址
  MOV    ES,AX        ; 存入ES寄存器

  如果程序中还定义了堆栈段STACK_SEG,也需要把段地址装入SS中:
  MOV    AX,STACK_SEG    ; 堆栈段地址
  MOV    SS,AX        ; 存入ES寄存器

  注意,在程序中不需要用指令装入代码段的段地址,因为在程序初始化时,装入程序已将代码段的段地址装入CS寄存器了。

  为了对段定义作进一步地控制,SEGMENT伪指令还可以增加类型及属性的说明,其格式如下:

  段名   SEGMENT  [定位类型][组合类型]['类别']
             .
            .
            .
  段名   ENDS

  [ ]中的内容是可选的,一般情况下,这些说明可以不用。但是,如果需要用连接程序把本程序与其他程序模块相连接时,就需要提供类型和属性的说明。

 表4.2.1 ·定位类型:说明段的起始边界值(物理地址)。

定位类型                说 明 
 BYTE              段可以从任何地址边界开始 
 WORD              段从字边界开始,即段的起始边界值为偶数 
 DWORD             段从双字的边界开始,即段的起始边界值为4的倍数 
 PARA              段从小段边界开始,即段的起始边界值为16 (或10H) 的倍数 
 PAGE              段从页边界开始,即段的起始边界值为256 (或100H) 的倍数 
注意:
  定位类型的缺省项是PARA,即在未指定定位类型的情况下,则连接程序默认为PARA。BYTE和WORD用于把其它段(通常是数据段)连入一个段时使用;DWORD一般用于运行在80386及后继机型上的程序。

表4.2.2 ·组合类型:说明程序连接时的段组合方法。
 组 合 类 型                说 明 
  PRIVATE       该段为私有段,连接时将不与其它模块中的同名段合并 
  PUBLIC        该段连接时将与其它同名段连接在一起,连接次序由连接命令指定 
  COMMON        该段在连接时与其它同名段有相同的起始地址,所以会产生覆盖 
  AT 表达式       段地址=表达式的值,其值必为16位但AT不能用来指定代码段 
  MEMORY        与PUBLIC同义 
  STACK        将多个同名堆栈段连接在一起,SP设置在第一个堆栈段的开始 

注意:组合类型的缺省项是PRIVATE。

 例4.2 在连接之前已定义两个目标模块如下:

  模块1   SSEG  SEGMENT  PARA  STACK
       DSEG1 SEGMENT  PARA  PUBLIC 'Data'
       DSEG2 SEGMENT  PARA
       CSEG   SEGMENT  PARA 'Code'

  模块2   DSEG1 SEGMENT  PARA PUBLIC 'Data'
        DSEG2 SEGMENT  PARA
        CSEG  SEGMENT  PARA 'Code'

  以上两个模块分别汇编后产生 .OBJ 文件,经连接程序连接后产生的 .EXE模块如下:

  模块1   CSEG  SEGMENT  PARA 'Code'
  模块2   CSEG  SEGMENT  PARA 'Code'
  模块1+2  DSEG1 SEGMENT  PARA PUBLIC 'Data'
  模块1   DSEG2 SEGMENT  PARA
  模块2   DSEG2 SEGMENT  PARA
  模块1   SSEG  SEGMENT  PARA STACK
 
4.2.1.2 存储模型与简化段定义伪指令

  较新版本的汇编程序(MASM5.0与MASM6.0)除支持完整段定义伪指令外,还提供了一种新的简单易用的存储模型和简化的段定义伪指令。

  1. 存储模型伪指令
  存储模型的作用是什么呢?存储模型决定一个程序的规模,也确定进行子程序调用、指令转移和数据访问的缺省属性(NEAR或FAR)。当使用简化段定义的源程序格式时,在段定义语句之前必须有存储模型 .MODEL语句,说明在存储器中应如何安放各个段。

  MODEL伪指令的常用格式如下:
   .MODEL 存储模型 

  2. 简化的段伪指令
  简化的段定义语句书写简短,语句.CODE、.DATA和.STACK分别表示代码数据段和堆栈段的开始,一个段的开始自动结束前面一个段。采用简化段指令之前必须有存储模型语句.MODEL。

  3.与简化段定义有关的预定义符号 
  汇编程序给出了与简化段定义有关的一组预定义符号,它们可在程序中出现,并由汇编程序识别使用。有关的预定义符号如下:
  (1)@code 由.CODE 伪指令定义的段名或段组名。
  (2)@data 由.DATA 伪指令定义的段名,或由 .DATA 、.DATA?、
    .CONST和 .STACK所定义的段组名。
  (3)@stack 堆栈段的段名或段组名。
 4.简化段定义举例


1. 存储模型伪指令
表4.2.3 MASM 5.0和MASM 6.0支持的存储模型:
存储模型        功 能        适应操作系统        
Tiny (微型)            所有数据和代码都放在一个段内,其访问都为NEAR型,整个程序≤64K,并会产生.COM文件。        MS-DOS        
Small (小型)            所有代码在一个64KB的段内,所有数据在另一个64KB的段内(包括数据段,堆栈段和附加段)。        MS-DOS
Windows        
Medium (中型)            所有代码>64K时可放在多个代码段中,转移或调用可为FAR型。所有数据限在一个段内,DS可保持不变。        MS-DOS
Windows        
Compact(紧凑型)            所有代码限在一个段内,转移或调用可为NEAR型。数据>64K时,可放在多个段中。        MS-DOS
Windows        
Large (大型)            允许代码段和数据段都可超过64K,被放置在有多个段内,所以数据和代码都是远访问。        MS-DOS
Windows        
Huge (巨型)           单个数据项可以超过64K,其它同Large模型。        MS-DOS
Windows        
Flat (平展型)           所有代码和数据放置在一个段中,但段地址是32位的,所以整个程序可为4GB。MASM 6.0支持该模型。        OS/2
WindowsNT        
注意:Small 模型是一般应用程序最常用的一种模型,因为只有一个代码段和一个数据段,所以数据和代码都是近访问的。这种模型的数据段是指数据段、堆栈段和附加段的总和。

  在DOS下用汇编语言编程时,可根据程序的不同特点选择前6种模型,一般可以选用SMALL模型。另外,TINY模型将产生COM程序,其他模型产生EXE程序。FLAT模型只能运行在32位x86 CPU上,DOS下不允许使用这种模型。当与高级语言混合编程时,两者的存储模型应当一致。

2. 简化的段伪指令
 表4.2.4 简化段伪指令的格式如下表:
简化段伪指令        功 能        注 释        
.CODE [段名]        创建一个代码段        段名为可选项,如不给出段名,则采用默认段名。对于多个代码段的模型,则应为每个代码段指定段名。        
.DATA        创建一个数据段        段名是:_DATA        
.DATA?        创建无初值变量的数据段        段名是:_BSS        
.FARDATA [段名]        建立有初值的远调用数据段        可指定段名,如不指定,则将以FAR_DATA命名。        
.FARDATA? [段名]        建立无初值的远调用数据段        可指定段名,如不指定,则将以FAR_BSS命名。        
.CONST        建立只读的常量数据段        段名是:CONST        
.STACK [大小]        创建一个堆栈段并指定堆栈段大小        段名是:stack。如不指定堆栈段大小,则缺省值为1KB        
3.与简化段定义有关的预定义符号

  下面的举例说明预定义符号的使用方法。在完整的段定义情况下,在程序的一开始,需要用段名装入数据段寄存器,如例4.1中的
       mov   ax,data_seg1
       mov   ds,ax
  若用简化段定义,则数据段只用.data来定义,而并未给出段名,此时可用
       mov   ax,@data 
       mov   ds,ax
  这里预定义符号@data就给出了数据段的段名。

 4.简化段定义举例
  
 例4.3
   .MODEL   SMALL
   .STACK   100H    ; 定义堆栈段及其大小
   .DATA         ; 定义数据段
       .
       .
       .
   .CODE         ; 定义代码段
 START:           ; 起始执行地址标号
   MOV     AX, @DATA ; 数据段地址
   MOV     DS, AX   ; 存入数据段寄存器
       .
       .
       .
   MOV    AX, 4C00H
   INT    21H
   END    START    ; 程序结束

  从例4.3可以看出,简化段定义比完整的段定义简单得多。但由于完整的段定义可以全面地说明段的各种类型与属性,因此在很多情况下仍需使用它。
 
4.2.2 段组定义伪指令

  段组定义伪指令能把多个同类段合并为一个64KB的物理段,并用一个段组名统一存取它。段组定义伪指令GROUP的格式如下:

  段组名 GROUP 段名 [, 段名 …]

  我们已经知道在各种存储模型中,汇编程序自动地把各数据段组成一个段组DGROUP,以便程序在访问各数据段时使用一个数据段寄存器DS,而GROUP伪指令允许用户自行指定段组。

例4.4 将两个数据段DSEG1 和DSEG2合并在一个段组DATAGROUP中。

   ;----------------------------------------------------

   DSEG1  SEGMENT WORD PUBLIC 'DATA'
       .
       .
       .
   DSEG1  ENDS
   ;---------------------------------------------------

   DSEG2  SEGMENT WORD PUBLIC 'DATA'
       .
       .
       .
   DSEG2  ENDS
   MOV     AX, @DATA ; 数据段地址
   MOV     DS, AX   ; 存入数据段寄存器
       .
       .
       .
   ;---------------------------------------------------
    DATAGROUP   GROUP DSEG1, DSEG2   ;组合成段组

   CSEG    SEGMENT PARA PUBLIC 'CODE'
         ASSUME CS : CSEG, DS : DATAGROUP
   START:  MOV   AX, DATAGROUP
         MOV   DS, AX          ;DS赋值为段组地址
            .
            .
            .
         MOV   AX, 4C00H
         INT   21H
   CSEG    ENDS
   ;-----------------------------------------------------
       END    START

  利用GROUP伪指令定义段组后,段组内统一为一个段地址,各段定义的变量和标号都可以用同一个段寄存器进行访问。
 
4.2.3 程序开始和结束伪指令

  在程序的开始可以用NAME或TITLE作为模块的名字,其格式为:
 
    NAME      模块名
    TITLE      文件名

  表示源程序结束的伪指令的格式为:
   
    END      [标号]
注意:NAME及TITLE伪指令并不是必需的,如果程序中既无NAME又无TITLE伪指令,则将用源文件名作为模块名。程序中经常使用TITLE,这样可以在列表文件中打印出标题来。

   END伪指令中的"标号"指示程序开始执行的起始地址。如果多个程序模块相连接,则只有主程序的END要加上标号,其他子程序模块则只用END而不必指定标号。例4.1~4.3的最后使用了END START伪指令。汇编程序将在遇END时结束汇编,并且程序在运行时从START开始执行。

4.2.4 数据定义及存储器分配伪指令

  80x86提供了各种数据及存储器分配伪指令,这些伪指令在汇编程序对源程序进行汇编期间,由汇编程序完成数据类型定义及存 储器分配等功能。

  数据定义及存储器分配伪指令的格式是:

 [变量] 助记符 操作数[, …,操作数] [ ;注释]

  下面介绍ORG伪指令以及常用的数据定义伪指令。

  ORG(origin)
  ORG伪指令用来表示起始的偏移地址,紧接着ORG的数值就是偏移地址的起始值。ORG伪操作常用在数据段指定数据的存储地址,有时也用来指定代码段的起始地址。

  DB(define byte)
  DB伪指令用来定义字节,对其后的每个数据都存储在一个字节中。DB能定义十进制数、二进制数、十六进制数和ASCII字符,二进制数和十六进制数要分别用"B"和"H"表示,ASCII字符用单引号(' ')括起来。DB还是唯一能定义字符串的伪操作,串中的每个字符占用一个字节。

  DW(define word)
  DW伪指令用来定义字,对其后的每个数据分配2个字节(1个字),数据的低8位存储在低字节地址中,高8位存储在高字节地址中,如下例中的变量DATA8的数据存储在0070字地址中,其中0070字节存储0BAH,0071字节存储03H。DW还可存储变量或标号的偏移地址。见左面DW伪指令的例子。

  DD(define doubleword)
  DD伪指令用来定义双字,对其后的每个数据分配4个字节(2个字)。该伪指令同样将数据转换为十六进制,并根据低地址存储低字节,高地址存储高字节的规则来存放数据。如下例DATA15的存储情况是:00A8:0F2H,00A9H:57H,00AAH:2AH,00ABH:5CH。
  用DD存入地址时,第一个字为偏移地址,第二个字为段地址。

  DQ(define quadword)
  DQ伪指令用来定义4字,即64位字长的数据,DQ之后的每个数据占用8个字节(4个字)。

  DT(define ten bytes)
  DT伪指令用来为压缩的BCD数据分配存储单元,它虽然可以分配10个字节(5个字),但最多只能输入18个数字,要注意的是,数据后面不需要加"H"。左面是DQ和DT的例子。

  DUP(duplicate)
  DUP伪指令可以按照给定的次数来复制某个(某些)操作数,它可以避免多次键入同样一个数据。例如,把6个FFH存入相继字节中,可以用下面两种方法,显然用DUP的方法更简便些。

 存入6字节的FFH
DATA20 DB 0FFH 0FFH 0FFH 0FFH 0FFH 0FFH;
DATA21 DB 6 DUP(0FFH)  

  DUP操作一般用来保留数据区,如用数据定义伪指令"DB 64 DUP(?)"可为堆栈段保留64个字节单元。DUP还可以嵌套,其用法见左例。

  PTR属性操作符
  PTR指定操作数的类型属性,它优先于隐含的类型属性。其格式为:

  类型 PTR 变量[ ± 常数表达式]

  其中类型可以是BYTE、WORD、DWORD、FWORD、QWORD或TBYTE,这样变量的类型就可以指定了。

  LABEL伪指令
  LABEL可以使同一个变量具有不同的类型属性。其格式为:

    变量名 LABEL 类型
  或 标号  LABEL 类型

  其中变量的数据类型可以是BYTE,WORD,DWORD,标号的代码类型可以是NEAR或FAR。

  数据定义及存储器分配伪指令格式中的"变量"是操作数的符号地址,它是可有可无的,它的作用与指令语句前的标号相同,区别是变量后面不加冒号。如果语句中有变量,那么汇编程序将操作数的第一个字节的偏移地址赋于这个变量。
  "注释"字段用来说明该伪指令的功能,它也不是必须有的。
  "助记符"字段说明所用伪指令的助记符。

  DB(define byte)
  请看下面数据定义的例子,注意DB定义的每个数据的存储情况,左边第一列是汇编程序为数据分配的字节地址,第二列是相应地址中存储的数据或ASCII字符(均用十六进制表示)。变量DATA7定义了3个数据和一个字符串,每个数据或串用","分开,它们分别存储在偏移地址002E开始的6个字节单元中。
表4.2.5
; DB 例子的列表文件
 0000  19       DATA1  DB  25        ; 十进制数 
 0001  89       DATA2  DB  10001001B      ; 二进制数
 0002  12       DATA3  DB  12H         ; 十六进制数
 0010                 ORG 0010H       ; 指定偏移地址为10h
 0010  32 35 39 31     DATA4  DB  '2591'         ; ASCII码数
 0018                ORG 0018H       ; 指定偏移地址为18h
 0018  00       DATA5  DB  ?          ; 保留一个字节
 0020                ORG 0020H       ; 指定偏移地址为20h
 0020  4D 79 20 6E 61 6D DATA6  DB  'My name is Joe' ; ASCII码字符
      65 20 69 73 20 4A
      6F 65
 002E  0A 10 02 31 30 42 DATA7  DB  10,10H,10B,'10B' ; 不同的数据类型         
DW(define word)
表4.2.6 
; DW 伪指令例子的列表文件
 0070               0RG 70H           ;指定起始地址
 0070  03BA      DATA8   DW  954         ; 十进制数
 0072  0954       DATA9    DW  100101010100B   ; binary
 0074  253F       DATA10 DW  253FH       ; 十六进制数
 0076  FFFB      DATA11 DW  -5         ; 负数
 0080              ORG 80H
 0080  0009 FFFF 0007 000C DATA12 DW 9,-1,7,0CH,00100000B,100,'HI'
      0020 0064 4849                    ; 各种类型数        
DD(define doubleword)
表4.2.7 
; DD例子的列表文件
 00A0                ORG 00A0H        ; 指定起始地址
 00A0  FF030000       DATA13 DD  1023          ; 十进制数
 00A4  5C960800       DATA14 DD  10001001011001011100B ; 二进制数
 00A8  F2572A5C      DATA15 DD  5C2A57F2H        ; 十六进制数
 00AC   23000000 89470300  DATA16 DD 23H,34789H,65533      ; 各种数据
      FDFF0000        
DT(define ten bytes)
 表4.2.8
; DQ、DT例子的列表文件
 00C0               ORG 00C0H
 00C0  C223450000000000 DATA17 DQ  4523C2H     ; 十六进制数
 00C8  4948000000000000 DATA18  DQ  'HI'         ; ASCII字符
 00D0  0000000000000000 DATA19  DQ  ?         ; 分配8个字节单元
 00E0               ORG 00E0H
 00E0  2998564379860000  DATA20  DT  867943569829    ; 压缩的BCD数
      0000
 00EA  0000000000000000 DATA21  DT  ?        ; 分配10个字节单元
      0000        
  对数据定义伪指令前面的变量还要注意它的类型属性问题。变量表示该伪指令中的第一个数据项的偏移地址,此外,它还具有一个类型属性,用来表示该语句中的每一个数据项的长度(以字节为单位表示),因此DB伪指令的类型属性为1,DW为2,DD为4,DQ为8,DT为10。变量表达式的属性和变量是相同的。汇编程序可以用这种隐含的类型属性来确定某些指令是字指令还是字节指令。

  下例中变量OPER1为字节类型属性,OPER2为字类型属性,所以第一条MOV指令应为字节指令,第二条MOV指令应为字指令。而第三条指令的变量表达式OPER1+1为字节类型属性,AX却为字寄存器,第四条指令的OPER2为字类型属性,AL为字节寄存器,因此,汇编程序将指示这两条MOV指令出错:"类型不匹配"。

   OPER1   DB ?, ?
   OPER2   DW ?, ?
     .
     .
     .     
        MOV  OPER1, 0   ;字节指令
        MOV  OPER2, 0   ;字指令
        MOV  AX, OPER1+1 ;错误指令:类型不匹配
        MOV  AL, OPER2  ;错误指令:类型不匹配

  PTR属性操作符
  下例中的两条MOV指令把OPER1+1的类型属性指定为字,把OPER2的类型属性指定为字节,这样指令中两个操作数的属性就一致了,汇编时就不会出错了。

   OPER1   DB ?, ?
   OPER2   DW ?, ?
     .
     .
     .
        MOV  AX, WORD PTR OPER1+1
        MOV  AL, BYTE PTR OPER2

  LABEL伪指令

  例如:
    BYTE_ARRAY   LABEL BYTE
    WORD_ARRAY   DW   50 DUP (?)
  在50个字数组中的第一个字节的地址赋予两个不同类型的变量名:字节类型的变量BYTE_ARRAY和字类型变量WORD_ARRAY。
  在程序中访问数组单元时,要按指令类型来选择变量,如下面两条指令:
     MOV    WORD_ARRAY + 2,0   ; 字指令,
                     ; 把该数组的第3个和第4个字节置0
     MOV    BYTE_ARRAY + 2,0   ; 字节指令, 
                     ; 把该数组的第3个字节置0
4.2.5 表达式赋值伪操作EQU

  EQU是一个赋值伪操作(伪指令),它给一个数据标号赋于一个常数值,但这个常数不占用存储单元。当这个数据标号出现在程序中时,汇编程序即用它的常数值代替数据标号。EQU可以在数据段之外使用,甚至可用在代码段中间。

   = 伪操作

  赋值伪操作"="的作用与EQU类似。它们之间的区别是,EQU伪操作中的标号名是不允许重复定义的,而=伪操作是允许重复定义的。

使用EQU操作的优点可从下面的例子中看出:

   COUNT   EQU  25
   COUNTER  DB   COUNT
     MOV  AL,  COUNT

  假定在数据段和代码段中要多次使用一个数据(如25),那么在编程时凡是用到25的地方都可用数据标号COUNT来表示。如果程序想修改这个数据,那么只需修改EQU的赋值,而无须修改程序中其它部分,如COUNTER和MOV语句就不必修改。
  
  EQU还可给表达式赋予一个名字,EQU的用法举例如下:

  DATA     EQU HEIGHT + 12   ; 地址表达式赋以符号名
  ALPHA    EQU 7        ; 常数赋以符号名
  BETA     EQU ALPHA-2     ; 把7-2=5赋以符号名BETA
  ADDR     EQU VAR + BETA    ; VAR+5赋以符号名ADDR。
  B      EQU [BP + 8]     ; 变址引用赋以符号名 B
  P8      EQU DS:[BP + 8]   ; 加段前缀的变址引用赋以符号名P8

  注意:在EQU语句的表达式中,如果有变量或标号的表达式,则在该语句前应该先给出它们的定义。如上例,ALPHA必须在BETA之前定义,否则汇编程序将指示出错。

  例如,  TMP  EQU 5
       TMP  EQU TMP+1 则是错误语句,因为TMP已赋值为5,就不能再把它定义为其它数值。
     而 TMP = 5
       TMP = TMP+1 则是允许使用的,因为=伪操作允许重复定义。第一个语句TMP的值为5,第二个语句TMP的值就为6了。

4.2.6 地址计数器与对准伪指令

  1.地址计数器$

  在汇编程序对源程序汇编的过程中,使用地址计数器来保存当前正在汇编的指令的地址。地址计数器的值在汇编语言中可用$来表示。

  当$用在伪指令的参数字段时,它所表示的是地址计数器的当前值

  2.EVEN伪指令

  EVEN伪指令使下一个变量或指令开始于偶数字节地址。

  3. ALIGN伪指令

  ALIGN伪指令使它后面的数据或指令从2的整数倍地址开始。其格式为:

    ALIGN 2n (n为任意整数)

1.地址计数器$ 

  汇编语言允许用户直接用$来引用地址计数器的值,例如指令:

     JMP   $+ 6

  它的转向地址是JMP指令的首地址加上6。当$用在指令中时,它表示本条指令的第一个字节的地址。在这里,$+ 6必须是另一条指令的首地址。否则,汇编程序将指示出错信息。

  当$用在伪指令的参数字段时,则和它用在指令中的情况不同,它所表示的是地址计数器的当前值。例如指令:

    ARRAY   DW 1, 2, $+ 4, 3, 4, $+ 4
  
  假设汇编时ARRAY 分配的偏移地址为0074H,则汇编后,$+ 4所在的两个字单元: 
  (ARRAY+4)=0078+4=007CH
  (ARRAY+0A)=007E+4=0082H

  应当注意,ARRAY数组中的两个$+ 4得到的结果是不同的,这是由于$的值是在不断变化的缘故。当在指令中用到$时,它只代表该指令的首地址,而与$本身所在的字节无关。

 2.EVEN伪指令

  例如:
    DATA_SEG  SEGMENT
          BYTE_DAT  DB ?
    EVEN
          WORD_DAT  DW 100 DUP (?)
    DATA_SEG  ENDS

  一个字的地址最好从偶地址开始,所以对于字数组为了保证它从偶地址开始,可以在DW定义之前用EVEN伪指令来达到这一目的。

 3. ALIGN伪指令

  例如:
.    
     ALIGN 4
     ARRAY DD 100 DUP (?)
     
  ALIGN伪指令保证了双字数组ARRAY地址边界从4的倍数开始。

  ALIGN伪指令是将当前偏移地址指针指向2的乘方的整数倍的地址,如果源地址指针以指向2的乘方的整数倍的地址,则不作调整;否则将指针加以一个数,使地址指针指向下一个2的乘方的整数倍的地址。

  当然,ALIGN 2和EVEN是等价的。

4.2.7 基数控制伪指令

  .RADIX伪指令

 .RADIX可以把默认的基数改变为2~16范围内的任何基数。其格式如下:
     .RADIX 基数值

  其中基数值用十进制数来表示。
 例如:
    MOV   BX, 0FFH    ;16进制数标记为H
    MOV   BL, 10000101B ;二进制数标记为B
    MOV   BX, 178    ;10进制为默认的基数,可无标记
    .RADIX 16       ;以下程序默认16进制数
    MOV   BX, 0FF    ;16进制为默认的基数,可无标记
    MOV   BX, 178D    ;10进制数应加标记D

  应当注意,在用 .RADIX 16把基数定为十六进制后,十进制数后面都应跟字母D。在这种情况下,如果某个十六进制数的末字符为D,则应在其后跟字母H,以免与十进制数发生混淆。

 4.3.1.汇编语言源程序语句的格式

  汇编语言源程序中的每个语句可以由四项组成,格式如下:

 [名字] 操作 操作数 [ ;注释]

  其中:
  名字项是指一个标号或变量。

  操作项是一个操作码的助记符,它可以是指令、伪指令或宏指令名。

  操作数项由一个或多个表达式组成,它提供为执行所要求的操作而需要的信息。操作数项可以是常数、寄存器、标号、变量或由表达式组成。

  注释项用来说明程序或语句的功能。";"为识别注释项的开始。";"也可以从一行的第一个字符开始,此时整行都是注释,常用来说明下面一段程序的功能。

  上面四项中带方括号的两项是可选项。各项之间必须用"空格"(space)或"水平制表"(Tab)符隔开。
  
  (1) 名字项
  (2) 操作项
  (3) 操作数项
  (4) 注释项

(1) 名字项
  源程序中用下列字符来表示名字:
  字母A~Z
  数字0~9
  专用字符 ?、·、@ 、-、$
  
  除数字外,所有字符都可以放在源语句的第一个位置。名字中如果用到·则必须是第一个字符。可以用很多字符来说明名字,但只有前面的31个字符能被汇编程序所识别。

  一般说来,名字项可以是标号或变量。它们都用来表示本语句的符号地址,都是可有可无的,只有当需要用符号地址来访问该语句时它才需要出现。

  · 标号:标号在代码段中定义,后面跟着冒号:,它也可以用LABEL或EQU伪操作来定义。此外,它还可以作为过程名定义,这将在以后的章节中加以说明。 

  · 变量:变量在数据段或附加数据段中定义,后面不跟冒号。它也可以用LABEL或EQU伪操作来定义。变量经常在操作数字段出现。

  (2)操作项
  操作项可以是指令、伪指令或宏指令的助记符。对于指令,汇编程序将其翻译为机器语言指令。对于伪指令,汇编程序将根据其所要求的功能进行处理。对于宏指令,则将根据其定义展开。宏指令在第七章中将会专门论述。

  (3) 操作数项 
  操作数项由一个或多个表达式组成,多个操作数项之间一般用逗号分开。对于指令,操作数项一般给出操作数地址,它们可能有一个,或二个,或三个,或一个也没有。对于伪操作或宏指令,则给出它们所要求的参数。

  操作数项可以是常数、寄存器、标号、变量或由表达式组成。

  (4) 注释项
  注释项用来说明一段程序、一条或几条指令的功能。对于汇编语言程序来说,注释项的作用是很明显的,它可以使程序容易被读懂,因此汇编语言程序必须写好注释。注释应该写出本条(或本段)指令在程序中的功能和作用,而不应该只写指令的动作。读者在有机会阅读程序例子时,应注意学习注释的写法,在编制程序时,更应学会写好注释。

4.3.2 表达式

  表达式是常数、寄存器、标号、变量与一些操作符相组合的序列,可以有数字表达式和地址表达式两种。在汇编期间,汇编程序按照一定的优先规则对表达式进行计算后可得到一个数值或一个地址。

  常用的表达式操作符介绍如下:

  (1) 算术操作符
  算术操作符有+、-、*、/ 和MOD。
  MOD是指除法运算后得到的余数,如19/7的商是2,而19 MOD 7则为5(余数)。

  (2) 逻辑操作符 
  逻辑操作符有:AND(与)、OR(或)、XOR(异或)、NOT(非)。
  逻辑操作符都是按位操作的,只能用于数字表达式中。逻辑操作符要求汇编程序对其前后两个操作数(或表达式)作指定的逻辑操作。

  (3) 关系操作符
  关系操作符有:EQ(相等)、NE(不等)、LT(小于)、GT(大于)、LE(小于或等于)、GE(大于或等于)。
  关系操作符的两个操作数必须都是数字或是同一段内的两个存储器地址。计算的结果应为逻辑值:结果为真,表示为0FFFFH;结果为假,则表示为0。

  (4) 数值回送操作符 (析值操作符)
  数值回送操作符有:TYPE、LENGTH、SIZE、OFFSET、SEG等。
  数值操作符把一些特征或存储器地址的一部分作为数值回送。

  · TYPE
  格式为:TYPE 表达式
  如果表达式是变量,则汇编程序将回送该变量的以字节数表示的类型:DB为1,DW为2,DD为4,DF为6,DQ为8,DT为10。如果表达式是标号,则汇编程序将回送代表该标号类型的数值:NEAR为 -1,FAR为 -2。如果表达式为常数,则应回送0。

  · LENGTH
  格式为:LENGTH 变量
  对于变量中使用DUP的情况,汇编程序将回送分配给该变量的单元数,而对于其他情况则送1。

  · SIZE
  格式为:SIZE 变量
  汇编程序应回送分配给该变量的字节数。但是,此值是LENGTH值和TYPE值的乘积。

  · OFFSET
  格式为:OFFSET 变量或标号
  汇编程序将回送变量或标号的偏移地址值。

  · SEG
  格式为:SEG 变量或标号
  汇编程序将回送变量或标号的段地址值。

  (5) 属性操作符
  属性操作符主要有:PTR、段操作符、SHORT、THIS、HIGH、LOW等。

  · PTR
  格式为:类型 PTR 符号地址
  PTR用来给已分配的存储地址(用符号地址表示)赋予另一种属性,使该地址具有另一种类型。
  类型可有BYTE、WORD、DWORD、FWORD、QWORD、TBYTE、NEAR和FAR等几种,所以PTR也可以用来建立字、双字、四字或段内及段间的指令单元等。

  · 段操作符
  段操作符用来表示一个标量、变量或地址表达式的段属性。
  格式为: 
  段寄存器∶地址表达式
  段名∶地址表达式
  组名∶地址表达式

  · SHORT
  用来修饰JMP指令中转向地址的属性,指出转向地址是在下一条指令地址的±127个字节范围之内。

  · THIS
  格式为: THIS 属性或类型
  THIS可以象PTR一样建立一个指定类型(BYTE、WORD、DWORD)或指定距离(NEAR或FAR)的地址操作数。该操作数的段地址和偏移地址与下一个存储单元地址相同。

  · HIGH和LOW
  称为字节分离操作符,它接收一个数或地址表达式,HIGH取其高位字节,LOW取其低位字节。

  · 操作符的优先级
  我们知道表达式是常数、寄存器、标号、变量和操作符的组合,在计算表达式时,应该首先计算优先级高的操作符,然后从左到右地对优先级相同的操作符进行计算。括号也可以改变计算次序,括号内的表达式应优先计算。
(1) 算术操作符

  算术操作符可以用于数字表达式或地址表达式中,但当它用于地址表达式时,只有当其结果有明确的物理意义时才是有效的结果。例如两个地址相乘或相除是无意义的。在地址表达式中,可以使用 + 或 - ,但也必须注意其物理意义。

  例如把两个不同段的地址相加也是无意义的。经常使用的是地址 ± 数字量,它是有意义的。例如SUM +1是指SUM字节单元的下一个字节单元的地址(注意:不是指SUM单元的内容加1),而SUM-1则是指SUM字节单元的前一个字节单元的地址。

  例: 如果要求把首地址为BLOCK的字数组的第6个字传送到DX寄存器,可用指令如下:
      MOV  DX, BLOCK+ (6-1) * 2

  例: 如数组ARRAY定义如下,写出把数组长度(字数)存入CX寄存器的指令。
      ARRAY  DW 1, 2, 3, 4, 5, 6, 7
      ARYEND DW ?

  其中ARYEND是为计算数组长度而建立的符号地址,所需指令如下:
      MOV   CX, (ARYEND-ARRAY)/2

  汇编程序在汇编期间将计算出表达式的值而形成指令:
      MOV   CX, 7 

  (2) 逻辑操作符 

   例:
    OUT PORT_VAL AND 0FEH, AL
  上例中PORT_VAL为端口号,OUT指令中的表达式说明只为偶数号端口输出数据。

  例:
    AND DX, PORT_VAL AND 0FEH
  上例在汇编时由汇编程序对指令中的表达式进行计算得到一个端口号,而在程序运行时,该指令的执行是把DX寄存器的内容与汇编程序计算得到的端口号进行"与"操作,结果保存在DX寄存器中。

  注意:表达式中的AND是运算符,汇编时由汇编程序计算出一个值;而AND指令是在运行时由CPU执行。

  (3) 关系操作符

  例如:
    MOV BX,((PORT_VAL LT 5) AND 20) OR ((PORT_VAL GE 5) AND 30)

  则当PORT_VAL < 5时,汇编结果应该是:
    MOV BX, 20

  否则,汇编结果应该是:
    MOV BX, 30

  (4) 数值回送操作符 

  · TYPE

  举例:
    ARRAY DW 1, 2, 3

  则对于指令ADD SI, TYPE ARRAY

  汇编程序将其形成为:
  ADD SI, 2

  · LENGTH

  举例1:
     FEES DW 100 DUP (0)
  对于指令MOV CX, LENGTH FEES

  汇编程序将使其形成为:
     MOV CX, 100

  举例2:
     ARRAY DW 1, 2, 3
  对于指令MOV CX, LENGTH ARRAY

  汇编程序将使其形成为:
     MOV CX, 1

  举例3:
     TABLE DB 'ABCD'
  对于指令MOV CX, LENGTH TABLE

  汇编程序将使其形成为:
     MOV CX, 1

  · SIZE

  例如:
     MOV CX, SIZE FEES
将形成为 MOV CX, 200

  又例如:
     MOV CX, SIZE ARRAY
将形成为 MOV CX, 2

  又例如:
     MOV CX, SIZE TABLE
将形成为 MOV CX, 1。

  · OFFSET

  举例:
    MOV BX, OFFSET OPER_ONE
  汇编程序将OPER_ONE的偏移地址作为立即数回送给指令,而在执行时则将该偏移地址装入BX寄存器中。所以这条指令的功能与指令
    LEA BX, OPER_ONE
  是等价的。

  · SEG

  举例:如果DATA_SEG是从存储器的05000H地址开始的一个数据段的段名,OPER1是该段中的一个变量名,则
     MOV BX, SEG OPER1

  将把0500H作为立即数插入指令。实际上,由于段地址是由连接程序分配的,所以该立即数是连接时插入的。执行期间则使BX寄存器的内容成为0500H。

  (5) 属性操作符

  · PTR

  举例:已有数据定义如下:
     TWO_BYTE DW ?
  可以用以下语句对这两个字节赋予另一种类型定义:
     ONE_BYTE EQU BYTE PTR TWO_BYTE
     OTHER_BYTE EQU BYTE PTR ( TWO_BYTE + 1)

  后者也可以写成:
     OTHER_BYTE EQU ONE_BYTE + 1
  这里ONE_BYTE和TWO_BYTE两个符号地址具有相同的段地址和偏移地址,但是它们的类型属性不同,前者为1,后者为2。

  此外,有时指令要求使用PTR操作符。例如用
     MOV [BX], 5

  指令把立即数存入BX寄存器内容指定的存储单元中,但汇编程序不能分清是存入字单元还是字节单元,此时必须用PTR操作符来说明属性,应该写明: 
    MOV BYTE PTR [BX],5
   或 MOV WORD PTR [BX],5

  · 段操作符
  
  例如,用段前缀指定某段的地址操作数
    MOV AX,ES:[BX + SI]

  · SHORT

  例如: JMP SHORT TAG ;转移属性为短转移 
        .
        .
        .
  TAG:  MOV... 

  · THIS

  例如: FIRST_TYPE EQU THIS BYTE
      WORD_TABLE DW 100 DUP (?)
  此时FIRST_TYPE的偏移地址和WORD_TABLE完全相同,但它是字节类型的;而WORD_TABLE则是字类型的。

  又如: START EQU THIS FAR
      MOV  CX, 100
  这样,MOV指令有一个FAR属性的符号地址START,这就允许其他段的JMP指令直接跳转到START来。

  · HIGH和LOW

  例如:
    CONST EQU 0ABCDH
  则 MOV AH, HIGH CONST
  将汇编成 MOV AH, 0ABH

  指令 MOV AH, LOW CONST
  将汇编成 MOV AH, 0CDH

  · 操作符的优先级

  操作符的优先级别从高到低排列如下:
  1. 在圆括号中的项,方括号中的项,结构变量(变量,字段。),然后是LENGTH、SIZE、WIDTH和MASK。
  2. 名:(段取代)。
  3. PTR,OFFSET,SEG,TYPE,THIS及段操作符。
  4. HIGH和LOW。
  5. 乘法和除法:*,/,MOD。
  6. 加法和减法:+,-。
  7. 关系操作:EQ,NE,LT,LE,GT,GE。
  8. 逻辑:NOT。
  9. 逻辑:AND。
  10. 逻辑:OR,XOR。
  11. SHORT。

  汇编语言程序中表达式的值实际上是由汇编程序计算的,而程序员应该正确掌握书写表达式的方法,以减少出错的可能性。

4.3.3 汇编语言源程序格式举例

  例1.完整段定义格式

  ; TITLE 文件名- 程序主要功能描述
  ;EQU 语句

  这里要说明几点:
  第一、其中关于建立过程的PROC和ENDP伪操作对将在以后的章节中说明。这里只要知道利用这一对伪指令把程序段分为若干个过程,使程序的结构加清晰就可以了。

  第二、本例只定义了最基本的代码段和数据段,如果程序中还需定义附加段和堆栈段,则定义的方式及建立段寄存器的方法是相同的,学员可自行设计。

  第三、本例把主程序建立为过程,由DOS调用该过程。进入程序后,首先把DS的内容和0作为段地址和偏移地址入栈,以便在程序结束时用RET指令返回DOS,这是一种工作方式。如果在主程序开始时没有用上面三条指令在堆栈中建立返回信息,则在程序结束时就不能直接用RET返回指令,而应该使用编号为4C的功能调用返回DOS,如下所示:
    MOV AX,4C00H
    INT 21H

  这种方式用得更加普遍。
  例1所给出的汇编语言源程序格式适用于MASM的各种版本,对于MASM5.0、6.0版可采用例2所示的汇编语言源程序格式。

  例2.简化段定义格式
例1.完整段定义格式

   ; TITLE 文件名- 程序主要功能描述
   ;EQU 语句



  ;* * * * * * * * * * * * * * * * * * * * * * * * * *
  datarea segment ;定义数据段

  ;数据定义语句

  datarea ends
  ;* * * * * * * * * * * * * * * * * * * * * * * * * * 
  prognam segment ;定义代码段
  ;---------------------------------------------------
     main   proc far     ;主程序
     assume  cs:prognam,ds  :datarea

 start:              ;程序起始执行地址
                  ;为程序返回DOS设置堆栈值
     push   ds        ;入栈保存原DS段址
     sub    ax,ax      ;设置偏移地址为0
     push   ax        ;入栈保存偏移地址

 ;置DS寄存器为当前数据段
     mov    ax,datarea    ;datarea段地址
     mov    ds,ax      ; 存入DS寄存器

 ;主程序部分
     ret            ;返回 DOS
main    endp           ;主程序结束


  ;----------------------------------------------------
sub1   proc    near      ;定义子程序

;子程序部分

sub1   endp            ;子程序结束
  ;------------------------------------------------------------------------------
prognam ends            ;代码段结束
  ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
end   start            ;汇编结束
例2.简化段定义格式

    .model  small      ;定义存储器模型
    .stack  100h       ;定义堆栈段
    .data           ;定义数据段

; ;数据定义语句
    .code           ;定义代码段
main  proc   far

start:
    mov    ax,@data     ;数据段地址
    mov    ds,ax      ; 存入DS寄存器

;主程序部分
    mov    ax,4c00h
    int    21h       ;返回DOS
main  endp            ;主程序结束 
    end    start      ;汇编结束
 


附:子程序的设计方法
  子程序又称为过程,相当于高级语言中的过程和函数。在一个程序的不同部分,往往要用到"类似"的程序段,即这些程序段的功能和结构形式都相同,只是某些变量赋值不同。此时就可以把这些程序段写成子程序形式,以便需要时调用它。

  有些程序段对于某个用户可能只用到一次,但它是一般用户经常用到的,如十进制数转换成二进制数,二进制数转换为十六进制数并显示输出等。对于这些常用的特定功能的程序段,经常编制成子程序供用户使用。

  此外,子程序结构还是模块化程序设计的重要工具,后面将介绍模块化程序设计方法。

 6.1.1 过程定义伪操作

  过程定义伪操作用在过程(子程序)的前后,使整个过程形成清晰的、具有特定功能的代码块。其格式为:

  procedure name PROC Attribute
  ……
  procedure name ENDP

  其中过程名为标识符,它又是子程序入口的符号地址,与标号的作用相同。
  属性(Attribute)是指类型属性,可以是NEAR或FAR。

  用户对过程属性的确定原则很简单,即:
  (1)如果调用程序和过程在同一个代码段中,则使用NEAR属性。(例6.1)
  (2)如果调用程序和过程不在同一个代码段中,则使用FAR属性。(例6.2)

例6.1  
  








   
        
   由于调用程序MAIN和子程序SUBR1是在同一代码段中的,所以SUBR1定义为NEAR属性。这样,MAIN中对SUBR1的调用和SUBR1中的RET就都是NEAR属性的。但是一般说来,主过程MAIN应定义为FAR属性,这是由于我们把程序的主过程看作DOS调用的一个子过程,因而DOS对MAIN的调用以及MAIN中的RET就是FAR属性的。
 
  过程定义也可以嵌套,一个过程定义中可以包括多个过程定义。

  例6.1的情况也可以写成如下的程序:
   







  

  








例6.2
     












  


   SUBT为一过程,它在两处被调用,一处是与它在同一段的SEGX段内,另一处是在另一段SEGY段内。为此,SUBT必须具有FAR属性以适应SEGY段调用的需要。SUBT既然有FAR属性,则不论在SEGX段或SEGY段,对SUBT的调用就都具有FAR属性。

 

 


附2:高级语言结构
在MASM6.0中引入了几种高级语言结构。

  目的是为了减轻汇编语言程序员的编程负担,同时也使程序易读。虽然在形式上它们和条件汇编或重复汇编有相似之处(其实这些宏指令都以 . 开始,所以形式上也和条件汇编与重复汇编有所不同),但实质上它们和条件汇编与重复汇编完全不同。条件汇编是由汇编程序来判断条件,从而产生一组或另一组汇编语言指令。重复汇编也是由汇编程序来判断重复次数并产生相应的汇编语言指令组。而这里所述的高级语言结构中给出的是宏指令,它们相当于标准的宏指令,汇编程序对它们的处理是按标准展开,形成一串指令来完成特定的操作。
MASM6.0中给出的高级语言结构有以下几种:
        .IF/.ELSEIF/.ELSE./ENDIF

       .WHILE/.ENDW
    
       .REPEAT/.UNTIL

       .REPEAT/.UNTILCXZ
    
       .BREAK

       .CONTINUE

7.4.1 .IF/.ELSEIF/.ELSE/.ENDIF

 .IF/.ELSEIF/.ELSE/.ENDIF相当于高级语言中的if,then,else,endif语句。
 其格式为:
 .IF 表达式1
 (汇编语言语句组1)
 .ELSEIF 表达式2
 (汇编语言语句组2)
 .ELSEIF 表达式3
 (汇编语言语句组3)
 .
 .
 .ELSE
 (汇编语言语句组n)
 .ENDIF

  其中,汇编程序展开 .IF宏指令时,产生一条比较指令cmp,用来完成其后的表达式所指定的操作,且当该表达式的值为真时(比较结果相等)执行跟在 .IF后的汇编语言语句组。如果在 .IF后还存在 .ELSEIF或 .ELSE指令,则在执行完这组语句后,还要产生一条转移指令,转移到 .ENDIF之后的第一条指令去执行。如果表达式的值为假(比较结果不等),则产生一条转移指令,转移到紧跟在 .IF之后的宏指令(可能是 .ELSEIF,也可能是 .ELSE或 .ENDIF)去执行。汇编程序对 .ELSEIF的处理和 .IF相类似。对 .ELSE和 .ENDIF的处理则比较简单,.ELSE只需产生其后跟着的汇编语言语句组,.ENDIF则为 .IF语句的结束。

例7.32 用汇编语言程序接收A、B、C、D中的任一字符均可在屏幕上给予回显。除此4字符外,接收任一字符均回显N。程序段如下,其中DISP为显示AL中字符的子程序。

      MOV      AH,1
      INT      21H
      .IF      AL=='A'
      CALL      DISP
      .ELSEIF    AL=='B'
      CALL      DISP
      .ELSEIF    AL=='C'
      CALL      DISP
      .ELSEIF    AL=='D'
      CALL      DISP
      .ELSE
      MOV      AL,'N'
      CALL      DISP
      .ENDIF

    汇编程序将产生如下代码:
      MOV      AH,01
      INT      21H
      CMP      AL,'A'
      JNZ      NOTA
      CALL      DISP
      JMP      DONE
   NOTA: CMP      AL,'B'
      JNZ      NOTB
      CALL      DISP
      JMP      DONE
   NOTB: CMP      AL,'C'
      JNZ      NOTC
      CALL      DISP
      JMP      DONE
   NOTC: CMP      AL,'D'
      JNZ      NOTD
      CALL      DISP
      JMP      DONE
   NOTD: MOV      AL,'N'
      CALL      DISP
   DONE:

  从这一例子中可以看出,这种高级语言结构非常简单,但它对提高程序的可读性和避免出错还是很有益的。
 
7.4.2 .WHILE/.ENDW

  .WHILE/.ENDW相当于高级语言中的WHILE语句。

  其格式为:
    .WHILE 表达式
    (汇编语言语句组)
    .ENDW
  当表达式值为真时,将执行汇编语言语句组中的语句,并重复测试直到表达式值为假时为止。如第一次测试表达式的值即为假,则将跳过这一语句组不予执行。.ENDW结束此循环。
例7.33 显示STRING串中的每个字符直到末字符$为止($不显示)。程序段如下,其中DISP仍为显示AL中字符的子程序,!= 表示不等。
      LEA     BX,STRING
      MOV     AL,[BX]
      .WHILE    AL !='$'
      CALL     DISP 
      INC     BX 
      MOV     AL,[BX]
      .ENDW
  汇编程序将产生以下代码:
      LEA     BX,STRING
      MOV     AL,[BX]
      JMP     COMPARE
  DISPLAY: CALL     DISP
      INC     BX
      MOV     AL,[BX] 
  COMPARE: CMP     AL,'$'
      JNZ     DISPLAY

  我们知道,高级语言中的WHILE语句总是先测试循环结束条件,以决定是否进入循环体。在这里,虽然测试条件放在循环体之后,但当程序中出现 .ENDW时,汇编程序将在循环体之前用一条JMP指令来保证先作测试,这样符合WHILE的含义,即如一开始就满足条件,则程序将立即结束而不会进入循环体。

7.4.3 .REPEAT/.UNTIL和.REPEAT/.UNTILCXZ

  这组宏指令相当于高级语言中的UNTIL语句。
 
  其格式为:
   .REPEAT
   (汇编语言语句组)
   .UNTIL 表达式
  和
   .REPEAT
   (汇编语言语句组)
   .UNTILCXZ 
  或
   .REPEAT
   (汇编语言语句组)
   .UNTILCXZ 表达式
  必须注意, .REPEAT循环主体长度必须小于127字节,否则汇编程序将指示出错。
  .REPEAT/.UNTIL先执行 .REPEAT以后的汇编语言语句,然后再测试表达式的值。如表达式为假,则转回执行循环,并返回测试直到表达式为真为止。所以,其结构与高级语言中的UNTIL循环类似。

  还有一种格式是 .UNTILCXZ后可以带表达式,这种表达式只限于使汇编程序产生比较相等或不等的代码,所以它的格式可以是以下几种:
  reg == reg reg != reg
  reg == memory reg != memory
  reg == constant reg != constant 
  memory == constant memory != constant

  其中reg表示寄存器,memory表示存储单元。对于带表达式的.UNTILCXZ 表达式,汇编程序除用CX作为循环计数器外,还将使用一个比较操作和LOOPE或LOOPNE指令来产生循环。也就是说,这种循环的结束条件为CX = 0或表达式值为真。
例7.34 编制程序作重复读入一字符并在屏幕上回显的操作,直到读入字符为CR(回车符)为止。(DISP为显示AL中字符的子程序)

     .REPEAT
     MOV       AH,1
     INT       21H
     CALL       DISP
     .UNTIL      AL==CR
   汇编程序将产生以下代码:
   RPT: MOV       AH,1 
     INT       21H
     CALL       DISP
     CMP       AL,0DH
     JNZ       RPT 

  .REPEAT/.UNTILCXZ同样产生UNTIL循环结构程序,但使用CX寄存器作为循环计数器,.UNTILCXZ则产生一条循环指令LOOP。请看下面的例子。

 例7.35 测试AX寄存器中的1的个数,并把结果存入DX中。程序采用使AX寄存器左移16次,每次测试符号位是否为1的方法来计数,循环次数则存放在CX中。

     MOV      CX,16
     MOV      DX,0
     .REPEAT
     TEST      AX,0FFFFH
     JNS      NEXT
     INC      DX 
  NEXT: SHL      AX,1
     .UNTILCXZ 
   汇编程序将产生以下代码:
     MOV      CX,16
     MOV      DX,0
   RPT: TEST      AX,0FFFFH
     JNS      NEXT
     INC      DX
  NEXT: SHL      AX,1
     LOOP      RPT 

 例7.36 本例的编程要求和例7.35完全相同,只是在程序的算法中再加上一个AX = 0的判断,也就是说每次AX左移后加上(AX)是否为0的判断。如为0,就可提前退出循环,而不必非等AX左移16次后才结束程序。编制程序如下:

     MOV       CX,16
     MOV       DX,0
    .REPEAT
     TEST      AX,0FFFFH
     JNS       NEXT
     INC       DX 
  NEXT: SHL       AX,1
    .UNTILCXZ    AX == 0
   可以看出这一程序与例7.35的程序只有末行有差异。汇编程序将产生以下代码:
    MOV       CX,16
    MOV       DX,0
  RPT: TEST       AX,0FFFFH
    JNS       NEXT
    INC       DX
 NEXT: SHL       AX,1
     OR        AX,AX
    LOOPNE      RPT 

 7.4.4 .BREAK和 .CONTINUE

  .BREAK可提前退出 .WHILE或 .REPEAT构成的循环。
其格式如下:

  .BREAK
 或
  .BREAK .IF 表达式

  第一种格式为无条件的退出循环。第二种格式则为当表达式的值为真时退出循环,否则不退出循环(如同无.BREAK语句一样)。

  .CONTINUE控制直接跳转到 .WHILE或 .REPEAT的循环测试条件。
它也有两种格式如下:

  .CONTINUE
 或
  .CONTINUE .IF 表达式

  第一种格式为无条件跳转,第二种格式则为当表达式的值为真时跳转。
例7.37 编写一程序:计算BUFFER缓冲区中除'空格'外的字符个数,把它存放在DX寄存器中。该缓冲区以$字符结束。程序如下:

      SUB      DX,DX
      LEA      BX,BUFFER
     .REPEAT
      MOV      AL,[BX]
      INC      BX
     .CONTINUE .IF AL ==''
      INC      DX
     .UNTIL     AL == '$'
   汇编程序将产生以下代码:
      SUB      DX,DX
      LEA      BX,BUFFER
   RPT: MOV      AL,[BX]
      INC      BX
      CMP      AL, ''
      JZ       NOCOUNT
      INC      DX
 NOCOUNT: CMP      AL,'$'
      JNZ      RPT
  可见程序能正常运行,但计数时会把$计算在内。为解决这一问题,可修改程序如下:
      SUB      DX,DX
      LEA      BX,BUFFER
     .REPEAT
      MOV      AL,[BX]
      INC      BX
     .BREAK .IF   AL == '$'
     .CONTINUE .IF AL == ''
      INC      DX
     .UNTIL 0
  汇编程序产生的代码如下:
      SUB      DX,DX
      LEA      BX,BUFFER
   RPT: MOV      AL,[BX]
      INC      BX
      CMP      AL, '$'
      JZ       EXIT
      CMP      AL, ' '
      JZ       NOCOUNT
      INC      DX
 NOCOUNT: JMP      RPT
   EXIT: 

  在这里,.BREAK的加入保证了程序的正确退出。这样一来 .UNTIL可以不再测试循环的结束条件,用 .UNTIL 0表示 .REPEAT/.UNTIL循环可以无穷尽地进行(亦即.UNTIL用一条JMP指令取代),这样该程序执行的结果便可以正确地计算BUFFER中除'空格'以外且不包括$在内的字符个数。
 
7.4.5 高级语言结构中使用的表达式

  在这一节里,我们将介绍高级语言结构中允许使用的表达式。

  表7.1说明这些表达式中允许使用的操作符。

  表达式的格式可分为以下两类:
  第一类用来测试条件码的值,如:
    ZERO?
    CARRY?
    OVERFLOW?
    SIGN?
    PARITY?

  如所指定的条件码位为1,则表达式的值为真。可以用 ! 放在表达式之前来测试相反条件。

  另一类是用寄存器reg、存储单元memory和常数作为操作数构成的表达式,如:
    reg
    memory
    reg op reg
    reg op memory
    reg op constant
    memory op constant

  其中,reg和memory用来判断其内容是否为零。如为零,则表达式的值为真。后面四种格式中的op可用表7.1中除 ! 外的操作符来作比较或逻辑操作。这些表达式之前也可加上 ! 来测试相反条件。

  这些操作符的优先级规定如下:
  ! 的优先级最高,其次是比较操作符,然后是&&,最后是||。也可以用括号来改变默认的优先级。

  下面我们再看一个包含复杂表达式的例子7.38

  最后要注意的是:汇编程序在作 > 或 < 的比较操作时是怎样判别相比较的操作数是带符号数还是无符号数的呢?

  在第四章里,我们已经说明在MASM6中除用DB、DW、DD等伪操作定义数据外,还可用与其等价的BYTE、WORD、DWORD等伪操作来定义数据。实际上,MASM6中还增加了专门用于定义带符号数的伪操作SBYTE、SWORD和SDWORD。如数据段中的变量已用带符号数的伪操作加以定义,则汇编程序将对该变量作带符号数比较。对于寄存器的内容则可在寄存器前加上前缀SBYTE PTR、SWORD PTR或SDWORD PTR来说明其内容为带符号数。如不说明,则汇编程序将默认其为无符号数。如在例7.38中,若将表达式改为
 (AX == VAR) && (SWORD PTR BX >= 10)

 (AX == VAR) || (SWORD PTR BX >= 10)
  则在汇编程序所产生的代码中,原来的无符号数转移指令JB将生成为带符号数转移指令JL。

  另外一个解决这个问题的办法是用ASSUME伪操作。例7.38中如保持原表达式不变,在程序中加入ASSUME BX:SWORD,则汇编程序产生的代码中也会用JL取代JB。

表7.1 高级语言结构表达式中的操作符
         操作符      含 义     操作符     含 义         
     = =        相等      <=     小于或等于
     !=         不等      &       位测试 
     >        大于      !       逻辑非 
     >=      大于或等于    &&      逻辑与 
     <        小于        ||       逻辑或
        
例7.38 数据段中定义一个变量VAR,程序段如下:

       .IF      (AX == VAR) && (BX >= 10)
       ADD      BX,VAR
       .ENDIF
       .IF      (AX == VAR) ||(BX >= 10)
       SUB      BX,VAR
       .ENDIF
     汇编程序将产生如下代码:
       CMP      AX,VAR
       JNZ      CONTINUE  ;判断(AX == VAR)是否为真
       CMP      BX,10
       JB      CONTINUE  ;(AX == VAR)成立,判断(BX >= 10) 是否为真
       ADD      BX,VAR   ;两个条件均成立,执行此指令
   CONTIUE: CMP      AX,VAR
       JZ      OPER    ;判断(AX == VAR)是否为真
       CMP      BX,10
       JB      DONE    ;(AX == VAR)不成立,判断(BX >= 10) 是否为真
    OPER: SUB      BX,VAR   ;两个条件之一成立,执行此指令
    DONE:

  这个例子除说明复杂表达式的使用方法外,还可以看出,汇编程序对于"与" 和"或" 这样的逻辑操作并不产生确定的代码,而是用对程序流程的控制来完成其操作要求。
评论次数(1)  |  浏览次数(1747)  |  类型(汇编作业) |  收藏此文  | 

[  martian   发表于  2008-11-24 13:43  ]

博主辛苦了!

 
 请输入验证码  (提示:点击验证码输入框,以获取验证码