什么是宏
还记得C中的define吗,宏是一个和差不多的东西。
C中的define可以替换掉一部分东西,然后在实际的实现中将define的别名换成实际内容,最后程序才能跑起来,这里也一样,我们可以定义一个宏,然后在编程中直接调用宏,便于我们的编程,主要是减少代码的长度。
要明白,在最后的计算机实现,其实还是将宏进行了展开,和子程序不一样,子程序才能真正减少行数。
格式
xyz MACRO x,y,z
MOV AL,x
MUL y
MOV z, AX
ENDM
这里我们定义了一个叫xyz的宏,将z赋值为x*y。
首先要有macro和endm作为边界,在第一行macro后面有一些参数,这些参数可以在宏中直接使用。
如果不需要参数这部分直接空着就行。
这个参数很有意思,可以是各种各样乱七八糟的东西,比如NULL、常数,寄存器、表达式、伪指令(甚至是伪指令的一部分,后面提到),就连其他宏名都可以传进来,有一点像函数指针那味。
另外形参和实参的个数也可以不相等。(实参是程序传入的,表示真实存在的;形参则是我们在宏中给的名字,只是一个叫法)
多余的实参会被忽略,多余的形参被置为空。
在调用的过程中,我们直接使用:xyz al,bl,dl就行。
一般来说,只要宏调用在宏定义之前就行,但我们还是建议在最开始,也就是在数据段前面。
宏操作符
这里介绍三个:"&"、"%"、"!"
这部分有一点奇怪,不过在高级语言中也有,虽然记不住哪种了……
&:表示连接。
Leap MACRO COND,LAB
J&COND LAB
ENDM
然后我们这样调用:Leap Z,there
在展开的过程中,会变成:JZ there。其中there是一个标号,表示跳转的位置。
真就是各种s操作。
我们不是说过可以传递表达式吗,如果是直接传入:disp 2*11-8
我们其实传入的是一个字符串,为了防止这种问题,我们采取加百分号的方式。
DISP MACRO X
String DB ‘ANSWER:’, ‘&X’,‘$’
ENDM
……
DISP %(2*11-8)
实际:string db ‘answer:’,‘14’,’$’
这里注意,X前的&符号是需要有的,目的是替代后形成新的符号或字符串
在C中printf有%d、%f,那么我们就不能使用百分号了,为了防止汇编中的这个问题,有了"!"的存在,相当于转义字符。(年纪大了,应该是没叫错吧)
DISP !%(2* 11-8) 的展开就变成了 String DB ‘ANSWER:’, ‘%(2* 11-8)’, ‘$’
标号问题
我们可能会在宏定义中使用标号,我们知道宏的本质就是在实现的过程中用定义代替我们的宏调用,另外我们也知道标号不能重复,重复必暴毙。
那如果是在宏中使用,我们有这样的办法:
在Marco伪指令之后加上一句:local 标号名
一定在Marco之后,另外一行可以加好几个。
在实际生成机器指令的时候,机器会以??+0000、??+0001,一直到ffff来标号不同的标号。
不玩文字游戏了,我们看一个叫next的标号:
ABSOL MACRO OPER
LOCAL NEXT
CMP OPER,0
JNS NEXT
NEG OPER
NEXT:
ENDM
ABSOL VAR
ABSOL BX
实际上的是这样的:
CMP VAR,0
JNS ??0000
NEG VAR
next0000:
CMP BX,0
JNS ??0001
NEG BX
next0001:
如果是local a,b,c 三个标号,实际展开:
??0000
??0001
??0002
??0003
??0004
??0005
……
嵌套问题
人类的本质不一定是复读机,还可能是套娃。
我们在a程序中调用宏b,宏b调用宏c,那么我们是不可以在a中调用c的,因为我们c的定义是在b内部完成的,也就是:
b marco **
c marco *
endm
endm
如果是将c定义在a中,那你随意。
最后copy一下子程序和宏的区分,看一下就行:
宏指令与子程序的区别:程序中重复出现的程序段既可以用宏指令,也可以用子程序(过程)来编写,两者都可以简化源程序的编写,而且都是一次编写,可多次调用。但是它们是两个完全不同的概念,它们之间有一些异同之处。
- 处理的时间不同:宏调用是在源程序被汇编时由汇编程序处理的;而子程序调用是在程序执行期间由CPU直接执行的。
- 处理的方式不同:两者都必须先定义后使用,但宏调用是用宏体替换宏调用伪指令,实参代替形参,源程序被翻译成目标代码后宏定义随着消失;而子程序则没有这样的替换操作,是以CALL指令将控制权由调用者转给子程序并执行。
- 参数处理不同:宏调用是以实参代替形参,参数的形式不受限制,可以是任何合法字符;子程序的参数需要寄存器或存储单元进行传递,而且需要附加的指令实现参数传递。
- 执行速度不同:子程序调用时需要执行CALL指令和RET指令,还要执行实现参数传递的附加指令,因而会比宏的执行速度稍慢。
- 占用的存储器空间大小不同:宏指令在每次调用时都要展开,把宏体中的程序段复制一遍,因而用宏指令编写的程序在目标代码中会重复出现相同或相似的程序段,占用内存空间较大;而子程序是由CALL指令调用的,无论调用多少次,子程序的目标代码只在程序中出现一次,目标代码相对较短。