汇编学习笔记(13) - 宏指令(MASM)
结构
说明:
是一堆数据的定义的集合
基本格式:
结构名称 STRUC
字段的定义
结构名称 ENDS
举例:
STUDENT STRUC
ID DW ?
SCORE DB 1
NAME DB 'STUDENTNAME'
STUDENT ENDS
定义变量:
变量名 结构类型 < 参数表 >
举例:
S1 STDUENT <1,50,'zhao'>
S2 STDUENT <1,60,'zhang'>
S3 STDUENT <1,70,'wang'>
结构使用:
1. MOV AX, P1.ID
2. MOV BX, OFFSET P1
MOV AL, [BX].SCORE
总结:
使用起来还是和C/C++ 的结构比较类似的,实际上 P1.ID 和 [BX].SCORE 的两种引用方式本质上是汇编器帮我们转换了地址。
记录
说明:
结构是将将一堆定义结合起来,方便管理,那么记录就是讲一个字或者一个字节拆分成各种按照位的定义
实际上记录就是一个字或者字节的按位定义,所以长度最长不超过16位,右对齐。
基本格式:
记录名称 RECORD 字段1:位宽=初始值,字段2:位宽=初始值,….
举例:
ABCD RECORD AA:5=12, BB:3=6, CC:4=3
内存结构:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 0 0 aa aa aa aa aa bb bb bb cc cc cc cc
定义变量:
变量名 结构类型 < 参数表 >
举例:
S2 ABCD <1,2,3>
S1 ABCD <1,2,3>
S3 ABCD <3,2,1>
记录使用:
WIDTH
返回一个记录整体的位宽或者一个字段的位宽
MOV AX, WIDTH ABCD
MOV AX, WIDTH AA
MASK
返回对应字段的掩码
MOV BX, MASK AA; BX = 0001111100000000B
总结:
和结构的功能有点类似,相当于在字节尺度尺度上的结构定义,相当于定义 标志位, 而MASK 就是提取标志位的掩码。
宏定义
说明:
宏定义的本质和C/C++ 一样就是字符替换,用定义的内容替换掉宏标识符
基本格式:
宏名称 MACRO 参数1,参数2,………
local 标号1, 标号2 ……
标号1: xxxxx
xxxxx
xxxxx
ENDM
举例:
M1 MACRO OPR, X, Y
LOCAL P1, P2
P1: OPR AX, BX
MOV AX, X
MOV BX, Y
TEST AX, BX
JNZ P1
JMP P2
P2: MOV AX, BX
ENDM
宏的使用:
M1 MOV, 5, 1
这条指令会被展开如下结果大致上就是字符替换
??0001: MOV AX, BX
MOV AX, 5
MOV BX, 1
TEST AX, BX
JNZ ??0001
JMP ??0002
??0002: MOV AX, BX
其中值得注意的是标号被替换成了 ??0001 ??0002 这是因为 宏如果在同一段代码里多次展开,标号也会跟着被多次展开,那么就会出现一段代码里同一个标号被多次定义,就会报错,所以如果在宏定义里面有标号定义,那么在 开头要使用 LOCAL 指令声明本地标号,这样汇编器会自动累加数字来代替标号
另外,使用的时候参数多传少传都可以,多传了就舍弃,少传了就默认用空代替,之后介绍的命令 IFB 可以判断参数是否为空
参数传递的说明:
& 连接参数用的,应该与C中的用法差不多,通常用于字符拼接以及字符串中
定义:
JUMP MACRO CON,LAB
J&CON LAB ;; 这里就是将 字符 J 和 CON 的传入的内容拼接起来
ENDM
使用:
JUMP NZ, EXIT
展开为
JNZ EXIT
同理如果是定义在'' 字符串中页需要用& 表明是参数
定义:
MSG MACRO A,B
A DB 'MR.&B'
ENDM
使用
MSG A1,HELLO
展开为
A1 DB 'MR.HELLO'
<> 字符串原样传递,如果字符串中报个各种字符,比如空格 特殊字符等,就用 <> 包起来,
汇编就会把他们统统当成一个完整字符串而不会去解析含义了
比如 < 包含各种内 容的字符,串--//123!@#$%^ > 中间有逗号,如果不用<> 汇编器会以为是两个参数
! 转义符号在上面的, 相当于c在字符串中的 \ , 这里比如 有一段字符串 1>2 ,如果用<> 包起来 就变成 <1>2>, 那就不对了,
汇编器会以为到1后边的> 就结束了,所以要用 <1!>2>, 用!来个后面的 > 转义.
% 在参数传递的时候是可以传入表达式的, 用%() 包起来,就是告诉汇编器闯入的是表达式的值而不是标的是本身。
比如
定义:
MSG MACRO A,B
A DB 'MR.&B'
ENDM
使用
MSG A1, 1+2
展开为
A1 DB 'MR.1+2'
使用
MSG A1, %(1+2)
展开为
A1 DB 'MR.3'
其他说明:
PURGE 消除宏定义
比如前面定义了 宏 MSG, 那么在使用 PURGE MSG 之后的代码里,MSG这个宏就不能使用了,但是之前还是能使用的。
EXITM 终止宏的展开
这个伪指令用在宏定义里面,当宏展开的时候遇到这个命令就停止展开了,相当于阶段宏的内容。
总结:
宏的功能有点像子过程调用,但是由于它是在汇编阶段就展开的,没有像CALL 一样的压栈转跳等的开销,所以实际上他比CALL高效,实际上就有点像C++ 中的 INLINE 函数。
宏中可以使用宏,宏定义中页可以嵌套定义宏,但是嵌套定义的宏,需要使用一次宏才会生效,比如
DEF MACRO A
A MACRO
push ax
ENDM
ENDM
如果不使用DEF宏定义,那么DEF宏定义不会被展开,那么根本就没有 A的宏定义,如果顺序是这样的
DEF DEF2
被展开为
DEF2 MACRO
PUSH AX
ENDM
这个使用宏定义中的宏定义才被真正定义
之后才可以使用DEF2的宏定义
重复汇编
说明:
用途就是相当于写了个循环
如下场景
db 0
db 0
.
20 个这样的内容,目的是定义20个byte变量,或者说就是定义 BYTE LIST[20]
.
db 0
db 0
那就可以用 重复汇编 宏指令了
基本格式:
RPET
格式
REPT NUM
XXXXXX
ENDM
说明
功能就是将里面的内容直接复制粘贴多少遍
比如
rept 5
db 0
endm
展开为
db 0
db 0
db 0
db 0
db 0
可以用来定义数据 比如 BYTE LIST[20], 直接就是
rept 20
db 0
endm
IRP
格式
IRP 待替换参数<参数表1,参数表2,参数表3,参数表4,参数表5,参数表6>
xxxxxxx
endm
说明
本质还是一样的,但是功能丰富了一点,在复制的时候,会逐一使用参数表内容替换待替换参数
比如
IRP P1<1,2,3,4,5,6>
DB P1
ENDM
展开为
DB 1
DB 2
DB 3
DB 4
DB 5
DB 6
IRPC
格式
IRPC 待替换参数,字符串
xxxxxxx
endm
说明
其实和IRP 是一样的,就是用参数表逐一替换,不同的是给出这里给出的参数是字符串,这个伪指令会用字符串的字符来逐一代替
比如
IRP P1 abcdef
DB P1
ENDM
展开为
DB 'a'
DB 'b'
DB 'c'
DB 'd'
DB 'e'
DB 'f'
总结:
这三个伪指令只要是在定义数据的时候使用的
条件汇编
说明:
和C/C++ 中的
#Ifxxx
#else
#endfi
是一样的东西
基本格式:
IFXXX
ELSE
ENDIF
IF/IFE
判断标的是的值,用法和LINUX SHELL中的用法蛮相似的
IF (表达式)
表达式 != 0 的时候内容有效
ELSE
表达式 == 0 的时候内容有效
ENDIF
表达式的计算使用 https://www.cnblogs.com/alwaysking/p/7623781.html 中介绍的内容
IFDEF/IFNDEF
和C/C++ 中的#ifdef 与#ifndef 意义完全相同
IFB
用在宏定义中的,用于判断某个参数 是否为空的
比如
MSG MACRO A,B
ifb <B>
A DB 'MR.&B'
else
A DB 'MR.UNKNOW'
endif
ENDM
INCLUDE
说明
没错与C/C++ 中的含义一致,就是在INCLIDE 的位置直接将INCLUDE的文件的拼接(复制粘贴)进来
用法
INCLUDE A.ASM
说明
include后面可以跟路径,和C/C++ 一样,汇编器会首先在当前目录找,之后会在编译参数指定的寻找路径查找文件