《80X86汇编语言程序设计教程》二 汇编语言及其程序设计初步

时间:2022-09-01 08:14:24

1、  语句格式:标号和名字不区分大小写

  指令语句格式:[标号:]  指令助记符  [操作数[,操作数]]  [;注释]

  伪指令语句格式:[名字]  伪指令定义符  [参数……参数]  [;注释]

  注:不同的汇编编译器支持的伪指令数量或者其表达形式会有差别

 

2、  数值表达式:

  a)  常数有:字母均不区分大小写

    1)  十进制:普通写法或者在数值后面加上字母d

    2)  十六进制:以数字字符开头(通常补0),数值后加h

    3)  二进制:在数值后面加b

    4)  八进制:在数值后面加q

    5)  字符串常数:用一对单引号或者一对双引号包含,单双引号可相互对应来定义另一者。如“aaa”bbb””使用’aaa”bbb”’来书写。此外,windows上,用0AH代表回车(’\n’),用0DH代表换行(’\r’),用24H代表字符串结束(’$’)。

  b)  算术运算符:+、-、*、\、MOD

  c)  关系运算符:相等(EQ)、不等(NE)、小于(LT)、大于(GT)、小于或等于(LE)、大于或等于(GE)。运算结果:关系成立则为0FFFFH,否则为0。如“mov  ax,1234H GT 1024H”汇编后为“mov  ax,0FFFFH”。

  d)  逻辑运算符:包括按位操作的或(OR)、与(AND)、非(NOT)和异或(XOR),还有左移(SHL)和右移(SHR)。

  e)  操作符:HIGH、LOW、LENGTH、SIZE、OFFSET、SEG、TYPE、WIDTH和MASK。其中:“HIGH  数值表达式”是数值表达式的高8位;而“LOW  数值表达式”是数值表达式的低8位。如“mov  ax,high 1234h+5”汇编后为“mov  ax,17H”(HIGH优先级高于+)。

  注意:操作符与运算符书写形式与支持与否更多的取决于编译器,emu8086的运算符书写形式同C语言,如:“MOD”运算写成“%”,“AND”为“&”,NOT为“!”,“XOR”为“^”,左移为“<<”,右移为“>>”,而关系运算符、HIGH、LOW操作符,貌似不支持(在RadASM中测试没问题)

 

3、  地址表达式:单个标号和变量都是地址,对应的直接寻址方式;在一个存储器地址上加减一个数字量,其结果仍然为地址,这相当于C中的指针移动。

 

4、  数据定义:

  a)  数据定义伪指令:[变量名]  数据定义符  表达式[,表达式,……,表达式]  [;注释]

    汇编后,内存排布顺序与定义顺序相同。数据采用小端模式存储。所有数据均以2进制补码形式存放。

    1)  定义字节数据项:db

      Count db    100

           db    0DH,0AH,’&’

    2)  定义字数据项:dw

    3)  定义双字数据项:dd

    4)  定义没有初值的数据项:用”?”,仅仅分配存储单元,而没有初值。

    5)  定义字符串:用引号括起来(单双均可,只要配对)。注意,即使用dw来定义字符变量或者字符串变量,每个字符占的内存还是一个字节。唯一区别是如“db ‘a’”这样的语句,字符a将占用两个字节,且a写在低位,高位为0。

    6)  定义其它类型数据项:8字节数据项定义符为DT、10字节数据项定义为DQ

  b)  重复操作符:count  DUP  (表达式[,表达式,……])

    只能使用在数据定义语句中,count为重复次数,要重复的内容在括号内,如有多个表达式,用逗号分隔,表达式中可重复使用dup,但有一定嵌套层次限制。如:

    dt  dw  1,5 dup (1,2,4 dup(0))

    注意:emu8086测试结果为无法嵌套,而重复了5次(1,2,4),很奇怪(这个是emu8086的问题,在RadASM中测试没问题)

 

5、  变量和标号:

  变量和标号均代表存储单元,变量表示的存储单元中存放的是数值,而标号表示的存储单元中存放的是代码。变量和标号在汇编编译以后均不再存在,它们都以地址的形式写入汇编后的机器码。有如下属性:

  a)  段值:变量或标号对应存储单元所在段的段值

  b)  偏移:变量或标号对应存储单元的起始地址的段内偏移

  c)  类型:变量有字节、字、双字;标号有近(段内标号)、远(段间标号)

  析值操作符:

    1)  SEG  变量名或标号

      返回变量或者标号所在段的段值

    2)  OFFSET  变量名或标号

      返回变量或者标号的段内偏移,使用OFFSET只能取得数据定义伪指令中定义的变量的有效地址,而不能取得一般操作数的有效地址(但是lea指令可以)。OFFSET返回值是汇编编译器计算出来的。

    3)  TYPE  变量名或标号

      返回变量或者标号的类型值:字节(1)、字(2)、双字(4)、近标号(-1)、远标号(-2)。Emu8086不支持该伪操作符(RadASM支持)。

    4)  LENGTH  变量名

      返回dup中定义的数组中元素的个数(没有使用dup则返回1,嵌套定义的,返回最外层的重复数),即count值。Emu8086不支持该伪操作符(RadASM支持)。

    5)  SIZE  变量名

      返回用dup定义的数组占用的字节数,SIZE 变量 = (LENGTH 变量)*(TYPE 变量)。Emu8086不支持该伪操作符(RadASM支持)。

         属性操作符:

    1)  操作符PTR:类型  PTR  地址表达式

      用指定类型来解析地址表达式指向的数据内容。类似高级语言的强制指针类型转换。类型可以是BYTE、WORD、DWORD、NEAR和FAR。使用PTR可方便访问一个变量的高低字节,假设VAR为一个字类型变量,那么:“mov  al,PTR BYTE VAR”访问低字节,而“mov  ah,PTR BYTE VAR + 1”访问高字节

    2)  操作符THIS:THIS  类型

      使用在符号定义语句中,定义一个具有类型、段值和偏移三属性的表示存储操作数的符号。如:

1 mb    equ    this byte
2 mw dw

      mb表示一个字节变量,它的段值与偏移与紧随其后的mw相同,所以对mb的访问就是对dw低字节的访问。这类似与C语言中的union结构。Emu8086不支持该伪操作符(RadASM支持)。

 

6、  符号定义语句:用符号表示常数、表达式。

  a)  等价语句EQU:符号名  equ  表达式

    不另外分配存储单元,定义的符号不能与其它符号相同,不能重定义

    1)  用符号代表常量或数值表达式。符号代表计算结果,不占用内存。有点类似C中宏定义,但是具有C++中const的安全性。很奇怪的一点是:

1 a    equ    10-2
2 b equ a * a + a
3 c equ 4

      在进行算术运算(如add)时,a的值为8,b的值为72,c的值为4,也就是当作立即数。而在传送指令时(如mov)却被汇编为直接寻址,如“mov    ax,a”汇编后变成“mov    ax,[8]”,b与a类似,而c还是当作立即数---在emu8086中测试,而在RadASM中测试发现没有这种异常,完全类似与C++中的const常量,很安全。

    2)  用符号表示一个串。如“c  que  ‘abc’”,然后“d  db  c,0ah,0dh,24h”

    3)  重新定义关键字或助记符。如:move  equ  mov

    4)  定义存储器操作数符号。如:

1 data        segment
2 flag dw 1234h
3 flag1 equ byte ptr offset flag ;取34h
4 flag2 equ byte ptr offset flag+1 ;取12h
5 data ends

      同1),这里也存在那个奇怪点,所以上面采用了0ffset时只能用在mov语句中。哪怕使用[flag1]都无法得到期望值---在emu8086中测试,在RadASM中没问题,注意定义时改为:flag1 equ   byte ptr flag ; flag2 equ   byte ptr flag+1。

  b)  等号语句:符号名 = 数值表达式

    这个是用符号名表示一个常数,与equ作用相近,但是它不能向上引用其它符号名,必须是可计算的数值表达式,可重定义。同样emu8086存在equ中1)的毛病。

  c)  定义符号名语句:符号名  LABEL  类型

    定义有符号名指定的符号,使该符号的段属性和偏移属性与下一个紧接着的存储单元的段属性与偏移属性相同,使该符号的类型为参数所规定的类型。如:

1 a    label    word
2 b db 100 dup(1)

    这个指令和THIS指令有区别?此外,它在emu8086也是各种奇葩,a定义以后,所有出现a的地方都是一个立即数,它的值就是段内偏移量。在RadASM中测试暂时未发现与equ定义的区别。

 

7、  段定义语句:按段组织程序和利用存储器

  a)  段开始与结束语句:段名被引用时表示对应段的段名

    段名    SEGMENT    [定位类型]    [组合类型]    [‘类别’]

    ;代码部分

    段名    ENDS

  b)  段使用设定语句:指定段寄存器与程序段的对应关系。源程序可有多条ASSUME语句,去重新确定段与段寄存器的关系,使用“段寄存器名:NOTHING”可以取消某个段寄存器与任何段的对应关系。

    ASSUME    段寄存器名:段名[,段寄存器名:段名……]

  c)  ORG语句:调整地址计数器的当前值。汇编用“$”表示地址计数器的值,如“JMP  $+6”表示跳转到当前指令起始字节(一条指令往往不止一个字节)后的6个字节处。

    ORG    数值表达式

 

8、  操作系统在装载没有堆栈段的程序时会指定一个堆栈段,在程序比较小的时候可以使用系统安排的堆栈段。指令语句与数据定义伪指令应该安排在段内,部分伪指令可安排在段外,如符号定义语句一般安排在程序定义起始处。在源程序最后要有结束语句:

  END    [标号]

  该语句一般是源程序最后一条语句,汇编编译器遇到它汇编即结束。END可带一个标号,它标志着程序的启动地址。要是程序是独立程序,则必带标号。如果仅仅是一个模块(不是主模块),则不应该带标号。

 

9、  顺序程序设计:指令按先后次序执行。简单查表法代码转换:适用于代码集合教小且转换关系复杂的场合

 1 //一个十六进制代码转对应七段数码管显示代码的程序
2 dseg segment
3 tab db 1000000b,1111001b,0100100b,0110000b ;七段代码表
4 db 0011001b,0010010b,0000010b,1111000b
5 db 0000000b,0010000b,0001000b,0000011b
6 db 1000110b,0100001b,0000110b,0001110b
7 xcode db 3h ;假设的16进制代码
8 ycode db 0h ;存放对应的7段代码
9 dseg ends
10
11 cseg segment
12 assume cs:cseg,ds:dseg
13 start:
14 mov ax,dseg
15 mov ds,ax ;段寄存器不能之间送立即数
16 mov bl,xcode ;取16进制数字码
17 and bl,0fh ;保证在0~F之间
18 xor bh,bh ;清bh
19 mov al,tab[bx] ;相当与al = [tab + bx]
20 mov ycode,al ;保存差别结果
21 mov ah,4ch
22 int 21h
23 cseg ends
24 end start

  运行结果是ycode = 30H,正是tab[3],8086提供了一个专门的查表指令:XLAT。该指令把寄存器bx的内容作为表(每项一个字节的表的起始地址),把寄存器al的值作为下标,取出表的内容送入al寄存器。表最大为256项。上述查表指令部分代码可如下编写:

1 mov  bx,offset tab
2 mov al,xcode
3 and al,0fh
4 xlat

 

10、 分支程序设计:一种对应与C语言的if语句的结构;一种对应与if-else语句的结构。还有一种用地址表实现多项分支的结构,对应C语言的switch语句。不再举例。

 

11、 循环程序设计:分初始化、循环体、调整、控制4个部分。循环通常采用计数控制与条件控制。这些东西也比较简单,联系的仅仅是对8086汇编语言的熟练度了,不侧重这点,也不再举例。