1、 这部分内容是和编译器密切相关的,学习一种语言,说白了就是学习编译器特性。就本章内容而言,对以后C语言深入底层的了解也许会有一点帮助。这里侧重点是编译器如何将各个“高级”数据结构转换为汇编语言的。
2、 结构
1) 定义格式如下
结构名 STRUCT
数据定义语句序列
结构名 ENDS
组成结构的字段称为结构的字段,相应的变量称为字段名,字段也可以没有字段名,字段名仅仅是代表从结构的开始到相应字段的偏移。注意三点:
第一,结构名与字段名必须唯一;
第二,结构仅仅是一个模板,只有在定义结构变量时才分配存储空间;
第三,结构说明时各字段可赋值,这是一个缺省的初始化值,可以在定义结构变量时重新赋初值来覆盖它们。
2) 结构变量的定义
[变量名] 结构名 <[字段值表]>
变量名缺省时不能使用符号来访问结构,字段值表用于初始化(覆盖结构定义时的初值),各字段初值用逗号分隔,如果字段值不指定,则使用结构定义时的初值(字段值表为空时表示全部使用结构定义时的初值)。须注意:对MASM而言,某个字段如果有多值,那么不能在定义结构变量时指定初值。
3) 访问结构字段
方式1:结构变量名.结构字段名
对应的直接寻址
方式2:结构变量地址偏移到某基址或变址寄存器,然后用“[寄存器]”代替结构变量名
对应的相对基址或变址寻址(注意,这种方式测试通不过)
方式3:结构变量地址偏移到某基址或变址寄存器,然后用“[寄存器.结构名.变量名]”,或者使用“[寄存器]. 结构名.变量名”
4) 测试
1 TestStruct struct
2 field1 db 0
3 field2 dw 3456h
4 field3 db "This is a struct!",0ah,0dh,24h
5 TestStruct ends
6
7 dseg segment
8 s1 TestStruct <>
9 ts2 TestStruct <,1111h,>
10 dseg ends
11
12 cseg segment
13 assume cs:cseg,ds:dseg
14
15 start:
16 mov ax,dseg
17 mov ds,ax
18
19 mov ax,ts1.field2 ;直接访问
20 ;汇编以后为:mov ax,word ptr [0001]
21 mov ax,ts2.field2 ;直接访问
22 ;汇编以后为:mov ax,word ptr [0018]
23 mov si,offset ts2
24 ;mov [si].field2,9999h ;相对基址变址寻址,无法通过编译
25 mov [si]. TestStruct.field2,9999h
26 mov [si. TestStruct.field2,9999h]
27
28 mov ah,4ch
29 int 21h
30 cseg ends
31 end start
3、 记录
为按二进制位存取数据或者提供信息提供了方便
1) 记录类型说明
记录名 RECORD 字段 [,字段…]
其中,字段为:字段名:宽度[ = 表达式]
其中宽度是位数,必须为常数,最大为16位,表达式的值作为相应字段的缺省值,如果不提供缺省值则默认置0。一个记录可以含有多个段,用逗号分隔。需要注意两点:第一,说明记录并不分配存储单元;第二,记录以字节为单位对齐,不足整字节的向整字节扩充,第一字段对应于字节的高位,最后一字段与字节低端对齐,补齐的位处于字节最高位,其值填0。如下记录:
TestRecord record ar:5 = 12,br:3 = 6,cr:4 = 3 ;对应二进制01100,110,0011
这里为5 + 3 + 4 = 12位,所以向两个字节补齐,到16位,补4位在开头,成为:0000 0110 0110 0011 = 06 63H,由于intel芯片采用小端存储,所以在内存的物理排布应该是:1100 0110 0110 0000 。在调试时是以字节为单位解析的,所以看到的应该是:63H 06H
2) 记录变量的定义
[变量名] 记录名 <[字段值表]>
其特性和结构的变量定义雷同。
3) 记录专用操作
a)操作符WIDTH:WIDTH 记录名 或 WIDTH 记录字段名
返回记录或记录字段以位为单位的宽度。
b)操作符MASK:MASK 记录名 或 MASK 记录字段名
返回一个8位或者16位的二进制数,其中与指定记录或者记录字段相应的位为1,其它位为0。
c)记录字段:不带操作数,直接返回该字段移动到记录最右端所移动的位数。如TestRecord中的cr = 0,br = 4,ar = 7。
4) 记录及字段的访问
由于8086并没有直接对位进行操作的指令,而记录操作符正是为了这种限制提供便利。如使用MASK可以方便的用and操作提取出感兴趣的字段,然后用记录字段配合shr可以很方便的把字段移动到字节最低端。再使用width做计数值配合loop可以方便的逐一访问各个位。不再举例。
4、 宏
存在的意义是为了避免书写麻烦,原理是编译时汇编编译器在有宏指令的地方一概使用宏定义的内容取代。与过程,特别是使用堆栈传递参数的过程相比,是一种用空间来换取效率的做法。
1) 宏指令的定义和使用
宏定义格式:
宏指令名 MACRO [形式参数表]
;宏定义体(指令、伪指令或宏指令)
ENDM
宏调用格式:宏指令名 [实参数表]
2) 宏指令的用途
a)缩短源代码:缩短书写时源代码的大小,不能缩短编译生成文件大小
b)扩充指令集:扩充指令集是机器指令集与宏指令集的并集。比如在函数执行开头,一般需要保存8个通用寄存器内容,于是可以编写一个pusha宏来实现,这就是一个扩充指令。
c)改变某些指令助记符的意义:宏指令可以与指令助记符或伪操作名相同,宏指令的优先级是最高的,而同名的助记符与伪操作就失效了。
3) 宏指令中参数的使用
a)灵活:可以是常数、寄存器、存储单元、表达式、操作码
b)参数个数可与定义时不一样:多余实参被忽略,而多余形参被置为空,但是语句必须有效
4)特殊宏运算符
适用于宏定义与宏调用,以及重复块
运算符 |
使用格式 |
定义 |
& |
&参数 |
强迫替换运算符 |
<> |
<字符串> |
字符串原样传递运算符 |
! |
!字符 |
文字字符运算符 |
% |
%表达式 |
表达式运算符 |
;; |
;;注释 |
宏注释 |
a)强迫替换运算符&
适用于参数在其它字符的紧前或紧后,或参数在带引号字符串中。如:
j&con lab ;其中con与lab为宏参数
那么调用时传递参数:con = nz,lab = here,宏展开为:jnz here
b)字符串原样传递运算符<>
适用于实参字串包含逗号或空格等间隔符,或者含某个特殊字符而只想表示字符本身的情况。如:
string db “&mess”,0ah,0dh,0 ;其中mess为宏参数
那么参数使用<this is a example>时展开为:string db “this is a example”,0ah,0dh,0
c)文字字符运算符
使其后的字符只作为一般字符,适用于实参中带有特殊字符而只想被解释为一般字符的情况。如上述宏语句使用参数<Can not enter > 99>时,由于>为特殊字符,所以须写成:<Can not enter !> 99>
d)表达式运算符%
宏调用时,使其后表达式的结果作为实参替换,而非表达式本身。如上述宏使用调用参数:
%(12 + 3 - 4) 被解释为:string db “11”,0ah,0dh,0
12 + 3 -4 被解释为:string db “12 + 3 -4”,0ah,0dh,0
e)宏注释
宏定义时使用,在宏展开后不出现该注释
5) 与宏有关伪指令
a)局部变量说明伪指令(适用于宏定义体):LOCAL 标号表
宏定义时如果使用了标号,那么多次展开将出现多个同样的标号,导致编译出错。这里的标号表在使用时就是宏体中使用到的各个标号名,用逗号分隔,但是在汇编展开后,总是把标号用唯一的符号(??0000至??FFFF)来代替,从而避免重定义错误。注意:该语句必须是宏定义体的第一条语句(其间,不允许有注释或者分号存在)。
b)清除宏定义伪指令:PURGE 宏名表
取消某些宏的定义,宏名表是各个宏名(用逗号分隔)构成的表。
c)终止宏扩展指令:EXITM
结束当前宏指令的扩展,使宏体中剩下的语句不被扩展,也适用于重复汇编。如果在一嵌套的宏内遇到该指令,则退出到外层宏。通常与条件伪指令一起使用,以便在规定条件下跳过宏内的最后语句(比如说用IFB判断参数为空)。
6) 宏定义的嵌套
a)宏定义体中调用宏
条件:先定义,后调用。
b)宏定义体中定义宏
适用于使用宏来定义宏的情况,如:
1 DefMac macro MacName,operator
2 MacName macro x,y
3 mov ax,x
4 operator ax,y
5 endm
6 endm
那么,执行下面两条语句后将定义两个宏:
1 DefMac madd,add ;用于加法操作
2 DefMac msub,sub ;用于减法操作
5、 重复汇编
用于定义一段重复完全相同或者几乎相同的一组语言为重复块。与宏的区别是无命名。
1) 伪指令REPT
rept 表达式 ;要重复的次数
;须重复的语句组
endm
其中,表达式可求出数值常数(16位无符号数)。如:把字符’A’到’Z’的ASCII码填入数组TABLE:
1 char = 'A'
2 dseg segment
3 TABLE label byte
4 rept 26 ;重复块开始,规定重复次数
5 db char ;须重复的语句块
6 char = char + 1
7 endm ;重复块结束
8 dseg ends
2) 伪指令IRP
IRP 形式参数,<实参1,实参2,…,实参n>
;须重复的语句
endm
重复次数和每次重复时使用的实参由列表决定:实参个数规定重复次数,每次重复时依次用对应位置的实参替换形参。如把0~9的平方值存入数组QUART:
1 dseg segment
2 TABLE label byte
3 irp x,<0,1,2,3,4,5,6,7,8,9>
4 db x*x
5 endm
6 dseg ends
下面语句将若干寄存器值压入堆栈:
1 IRP REG,<AX,BX,CX,DX>
2 push REG
3 endm
3) 伪指令IRPC
类似IRP,但是实参列表是一个字符串:
IRP 形式参数,<实参1,实参2,…,实参n>
;须重复的语句
endm
须注意:如果字串有空格,逗号等分隔符,须要尖括号括起来。将AX,BX压入堆栈如:
1 IRPC REG,AB
2 push REG&X
3 ENDM
6、 条件汇编
决定是否汇编某段源程序。目的:汇编时改变条件实现不同功能程序;增强宏定义能力;改善汇编效率
1) 条件汇编伪指令
IFxxxx 条件表达式
语句组1
[ELSE
语句组2]
ENDIF
IFxxxx可以是:IF、IFE、IFDEF、IFNDEF、IF1、IF2、IFB、IFNB、IFIDN、IFDIF
2) IF和IFE
IF和IFE其后都接一个表达式,不能包含向前引用,其结果要是常数。例如:
1 SHIFTL macro REG,N
2 COUNT = 0
3 REPT N ;重复N次
4 SHL REG,1 ;左移
5 COUNT = COUNT + 1
6 IF COUNT GE N ;如果计数值不小于N,退出重复汇编
7 EXITM
8 ENDIF
9 ENDM
10 INC REG ;REG值加1
11 ENDM
当然,这里的REPT重复汇编内部的IF条件汇编其实完全可以不用,只是为了说明用法。
3) IFDEF与IFNDEF
其后都是跟一个符号。如果符号已定义或被说明为外部符号,则IFDEF满足,否则IFNDEF为真。这个和C语言中的ifdef和ifndef很像。
4) 伪指令IF1和IF2
IF1:若第一遍扫描则条件为真。IF2:若第二遍扫描则条件为真
5) 条件汇编与宏的结合
能扩大宏作用范围(比如:对宏扩展时,可根据不同参数扩展为不同指令)。汇编提供专门用于测试宏参数的条件汇编伪指令:
a)IFB和IFNB
“IFB <行参>”表示如果宏调用时没有使用实参来代替该形参,那么条件为真,而IFNB正好相反。
b)IFIDN与IFDIF
一般用于宏定义内,其后接“<形参1>,<形参2>”,对于IFIDN与IFIDNI来说,如果文本参数1与文本参数2相等,则条件满足,后者与前者的区别是后者忽略大小写。而IFDIF与IFDIFI刚好相反,串不等则条件满足,它们二者的区别也是后者忽略大小写。
6) 源程序的结合
a)伪指令INCLUDE:INCLUDE 文件名
将指定文本文件加入该文本行。汇编解释器先在/I指定目录找文件,然后再到当前目录找,对于MASM还会在当前环境变量中找。与C不同的是,INCLUDE语句的位置有要求,如果文件名中定义的是函数,而且没有定义段,没有使用远过程,那么INCLUDE语句必须包含在调用语句所在的段名段,原则是它们必须在同一个段。其本质是INCLUDE只是读出文件内容写在了INCLUDE伪指令开始的行。
b)宏库的使用
把一些有价值的经常使用的宏定义集中在一个文件中,这个文本文件叫宏库。这样就可以使用INCLUDE包含。须注意,对于宏库,可只在第一遍扫描加入,而第二编扫描过滤掉(只有宏库可以这样),能加快编译速度。在INCLUDE宏库时,可以使用purge取消其中一些不需要的宏定义,它并不影响宏库。
这两部分比较简单,不再赘述。