masm32尽好资料[宝典]

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

MASM32绝好资料
Introduction to Assembler (2)
MASM Reference (11)
宏参考MACRO Reference (11)
伪指令参考 (16)
语法参考 (31)
其它参考 (37)
寄存器大全 (37)
标志寄存器Processor Flags (37)
80位数据寄存器Stack of 80-bit Data Registers (38)
Ascii 字符表Ascii Characters (39)
奔腾指令优化参考Pentium Optimisation (40)
调用格式Calling Conventions (43)
命令行工具Command Line Tools (43)
Introduction to Assembler
汇编概述
直线内存模式FLA T Memory Model
32位(二进制位,bit。

下同)Windows程序使用“直线内存模式”。

这样的程序实际只有一个段,包括程序的所有代码和数据,而且只能在386以上的INTEL处理器上运行。

早期的16位程序使用段和偏移来代表地址,每个段的大小不能超过64KB,而“直线内存模式”只有偏移,它的大小是4G,即0XFFFFFFFF,这种地址表示方式使汇编程序更易于开发。

在FLA T模式下,所有的段寄存器都自动设置成为同一个值,32位Windows程序中段/偏移形式的地址在不是必须的。

对于DOS程序员来说,32位Windows PE格式的应用程序好象是.COM程序一样,使用单一的段包括了代码和数据,只操作偏移地址,而不是用段/偏移地址。

直线内存模式时,应用程序在4G内存空间内所有的引用都是近程地址(NEAR code addressing)和近程数据(NEAR data addressing)。

GS和FS段寄存器在普通的FLA T模式应用程序中并不使用,一般是操作系统用于操作实例(used in instances)。

保护模式内存Protected Mode Memory
DOS是真实地址内存模式,这样应用程序可能改写操作系统的代码,而引起系统瘫痪。

例如在编写CGA(注:早期显示模式,显示缓存地址在B800处开始)程序时,使用B800内存区域,如果LOOP循环写的不对,就可能改写高位内存中的DOS代码或BIOS设置,造成死机。

设计保护模式就是用来防止这样的事情发生,内存管理器控制并保护应用程序访问的地址,阻止应用程序访问没有权限的内存区域。

16位Windows是模拟多任务的操作系统,它的应用程序也可能改写其它应用程序甚至是操作系统的内存。

改写了操作系统代码,应用程序调用某个系统函数时就会引起死机,最常见的就是“蓝屏”(注:Windows95/98常见故障)死机。

如果开发的应用程序本身存在逻辑混乱,应该是一个“黑屏”死机。

更改硬件、在多任务基础上使用硬件的时候,保护模式会更加可靠。

因为汇编语言编程时允许读写任何地址,所以要留心读写的句柄和地址。

如果分配了10K内存来读而却试图读20K 内容,就会引起页错误。

也可能因为使用变量或寄存器指向的地址超出了应用程序权限范围,而引起内存读写失败。

操作系统会将页异常传递给造成出错的应用程序,如果应用程序没有处理异常,操作系统就会关闭应用程序。

这给应用程序提供一个在保护模式下,得到有读写权限的地址范围的一种方法。

操作数Instruction Operands
操作数被用来做汇编指令助记符(mnemonic)中的参数。

一条指令可能有0到3个操作数,有两个操作数的逻辑或数学指令当中,位于右边的是源操作数,左边的是目的操作数。

如mov eax,1;mov是汇编代码助记符,eax是目的操作数,1是源操作数。

指令的操作结果是将1放入eax。

在其它处理器的汇编语言中,操作数使用顺序不同的都会特别注明。

指令码和助记符Opcodes and Mnemonics
Intel极兼容处理器的硬件层面,内建指令(instructions)叫opcodes(注:二进制的指令码),以位(bit)表示,最小的处理单元是字节(BTYE)。

这样的二进制的指令码程序可以用16进制的编辑器来编写,但它太繁杂,如果将32位值hex:56 a7 00 fe放入eax寄存器,那就要找指令码A1(mov eax),后面跟上fe 00 a7 56。

这样的复杂性谁也受不了,于是很早以前发明了助记符,一直用到现在的32位(已经64位了)汇编时代。

注意:相同的助记符表示的指令码可能不同,如mov eax,V ar1和mov V ar1,eax两条指令的指令码分别是A1H和A3H。

助记符让程序员编写汇编程序时简便易记。

寄存器Registers
处理器内部的高速数据处理单元是寄存器,也是处理器内部的存贮单元。

执行指令时,寄存器比内存操作数快的多。

INTEL处理器内部的寄存器数量是有限的,普通寄存器8个:EAX,EBX,ECX,EDX,ESI,EDI,ESP和EBP。

编程时经常把ESP和EBP单独列出来,因为它们主要在子过程的进入和退出时用来指示堆栈和参数,实际上只剩6个寄存器可以使用。

ESI和EDI也可以以字节访问,如用读SI的方式来访问ESI的低字。

清楚寄存器本身可以使用什么样宽度类型的数据非常重要。

通常带有整数操作数的指令使用三种类型数据:BYTE:单字节8位;WORD:双字节16位;DWORD:四字节32位。

例如EAX寄存器,AL或AH 表示使用8位二进制数据,是AX的低字节或高字节,AX表示使用16位数据,它是EAX 的低字,而EAX表示32位数据。

EAX寄存器从右到左依次是0位到31位(下同)。

访问0到7位用AL表示,8到15位用AH表示,0到15位用AX表示。

想访问16到31位要通过ROL eax,16;或ror eax,16;等移位指令,再读AX寄存器。

mov eax,cl;这条指令是非法的,两个操作数数据宽度不对。

如果必须进行这样的操作,需要进行符号扩展,使用有符号扩展功能的指令:movzx eax,cl;扩展为无符号整型或:movsx eax,cl;扩展为有符号整型。

旧指令集中的cbw或cwde指令可以对AL或AX数据进行符号扩展。

寄存器保护Register Preservation
在x86系列处理器中,普通目的寄存器共8个,除ESP和EBP用来管理函数调用和退出之外,还有6个普通目的寄存器。

保护寄存器是指程序在使用Call调用子程序或者中断时,应该保护哪些寄存器的数据。

编写32位Windows应用程序时,使用寄存器有一个约定,即访问操作系统函数WIN32API时的一个接口标准:6个自主使用的寄存器中,3个可以自由更改:EAX,ECX,和EDX;另外3个必须保护:EBX,ESI和EDI。

如果函数需要使用应该保护的3个寄存器,就必须在使用前将它们的值保存,用后再进行恢复,如:
TestProc proc var1:DWORD,var2:DWORD
push ebx
push esi
push edi
; -------------------------------------
; write code that uses EBX ESI and EDI
; -------------------------------------
pop edi
pop esi
pop ebx
ret
TestProc endp
调用WIN32API函数后,可以自由更改的3个寄存器的值可能被WIN32API更改了,所以这3个寄存器中的有用数据在调用之前要保存好。

应用程序使用3个被保护的寄存器,即使调用WIN32API时,这3个寄存器的内容不会因为调用而受影响,如做计数器(或存贮其它有用数据)都是有效的。

它们在WIN32API中被保护了,意图是减少在代码中重复进行存贮和恢复。

汇编程序中也可以手工编写函数的入口和出口,这是汇编的一个复杂之处,因为这里的错误非常容易使操作系统死机。

所以使用ESP和EBP是非常麻烦的事。

下面是保护ESP和EBP 的例子:
call procname
procname:
push ebp ; preserve base pointer
mov ebp,esp ; stack pointer into ebp
; write your assembler code here
mov esp,ebp ; restore stack pointer
pop ebp ; restore base pointer
ret
label:
还有其它保护ESP和EBP寄存器的方式,这看个人喜好和使用约定。

需要注意的是使用其它方式或约定时,一定要将这两个寄存器的内容同时保存和恢复。

使用个人约定方式,需要手工计算堆栈中每个函数参数和使用局部变量的偏移量,传递的参数开始于[EBP+8],局部变量在堆栈中以EBP为基准按相反的顺序排列,如果想将传递过来的一个参数放入EAX寄存器如mov eax,var1 ,那么实际是这样的指令mov eax,[ebp+8](注:用椎栈传递参数时,第一个参数位于最底端,相对于基指针的位置)。

使用汇编/API混合编程时,如果不知道API用了哪个寄存器,有一对非常有用的汇编指令将所有使用寄存器和标志寄存器保存起来:PUSHAD和POPAD。

这对指令在编写应用程序时并不是最优化(指速度)的,但在开发过程当中相当方便。

使用条件转移指令时,使用PUSHFD 和POPFD指令对可以保护标志寄存器。

寄存器的数据类型Data Types In Register
有三种操作数可以放入寄存器:立即数,内存数据,其它寄存器数据。

立即数指数字或ASCII字符。

字符的数值是其ASCII码。

如:mov al,"a";
内存操作数是某种形式的内存地址。

一般习惯将内存操作数两边加[]号,用以区别其它操作数和地址。

如:mov al,[esi];再如:mov edx,lpMemvar 将变量的地址放入edx(注:masm32\HELP\Introduction to Assembler。

这几处例子指令相互矛盾,内存数据和内存地址没有分开)。

用其它寄存器作为操作数时简单拷贝其内容。

如mov ecx,edx;
不能将一个内存操作数用一条指令放入另一个内存操作数。

如:mov mV ar,lpMem不合法。

如果有空闲的寄存器,可以用两条指令实现这个动作:mov eax,lpMem和mov mV ar,eax。

也可以不用寄存器:push lpMem和pop mV ar。

堆栈The Stack
堆栈用于函数存贮数据、返回地址,或者为函数传递参数用的一块临时存贮数据的内存区域。

堆栈通常用两条指令来操作:PUSH和POP。

堆栈内的数据读写按后进先出的顺序。

堆栈的下一个可写的位置叫栈顶(注:ESP总是指向最后一个压入的数据的低位字节,也这是说ESP 的值是这个字节的地址)。

压入一个数据,处理器会减小堆栈指针ESP的值,从栈顶读(弹出)一个值,就增加堆栈指针的值,读写几个字节(2或4个)就增减几个字节。

WIN32应用程序保护寄存器有约定,通常用EBP和ESP来控制函数进入和退出的堆栈地址,而3个需要保护的寄存器,进入函数之后要先保存它们,在函数RET之前恢复。

堆栈具有出入平衡的特性,程序保存多少数据,就要弹出多少数据,否则容易引起死机(注:因为函数返回地址等一些重要数据也存贮在堆栈中,函数压入和弹出的数据不等,造成程序跳转混乱,导致崩溃)。

使用堆栈时也有一些技巧。

比如一次压栈一个32位数据,可以分两次弹出两个16位数据,堆栈仍是平衡的。

但使用堆栈时一定要小心。

地址和指针Addressing and Pointers
汇编中一定要区分开变量的地址和变量的值。

地址是内存位置,值是那个位置存贮了什么数据。

从一个地址获得数据的方法叫“反映射”(dereferencing)。

如move eax,[eax];使用了方括号,表示将EAX寄存器指示的内存地址中的数据内容再装入到EAX中,这种方式适用于所有32位寄存器。

方括号括起来的寄存器操作数等同于内存操作数。

在高级语言中,经常用指针传递复杂的数据。

lea eax,MyV ar;将变量的地址放入EAX寄存器,接下来用指令mov lpMyV ar,EAX;将地址放入变量中,lpMyV ar就成为一个指针。

计算地址表达式Calculating Affective Addresses
数据量相当大或是查表访问的时候,用mov ecx,[eax];等形式的指令显得不太方便。

Intel 定义了一个更有力,但十分复杂的机制解决地址计算的问题。

X86处理器数组成员的格式定义:[Base Address + Index*Scale + Displacement],使用寄存器表示成:[ebx + ecx*4 +8]。

ebx 是基址,ecx是索引,4代表数据类型尺寸,8代表字节偏移。

并不是所有的数据都必须这样表示,如果使用的是字节数组,可以直接使用基址和索引,如:mov al,[ebx+ecx]。

在实际的应用程序中如何表示是可选的,包括寄存器和正负符号。

库Libraries
将常用的函数写成库模块,使用起来更加方便。

模块代码的编写与普通代码编写类似。

应用程序中调用库的方法是“包含”。

写库的时候要注意将不同用途的函数分开,避免用不到的函数编译到应用程序中,这样的原则在库模块的编写中叫“间隔尺寸”。

写一个模块非常容易,如:
.386 ; set processor type
.model flat,stdcall ; memory model & calling convention
option casemap :none ; case sensitive
; --------------------------------
; Include any needed INCLUDE files
; or procedure prototypes.
; --------------------------------
; include \masm32\include\windows.inc
; include \masm32\include\gdi32.inc
; include \masm32\include\user32.inc
; ---------------------------------------------------------
; Write the prototype for the procedure name below ensuring
; that the parameter count & size match and put it in your
; library include file.
; ---------------------------------------------------------
; Y ourModule PROTO :DWORD,:DWORD etc.....
.code
; -----------------------------------------------------
Y ourModule proc par1:DWORD,par2:DWORD etc.....
ret
Y ourModule endp
; -----------------------------------------------------
一个或多个模块编译成库的最简单办法,是在目录中创建一个批处理文件,内容如下:
@echo off
\masm32\bin\ml /c /coff *.asm
\masm32\bin\lib *.obj /out:yourlib.lib
: The following line works as well
: \masm32\bin\link -lib "*.obj" "/out:yourlib.lib"
库建立之后,要写库中每一个模块协议类型(prototypes)(注:调用规则,或者叫做接口规则)的包含文件(include file)。

代码中用到库时使用INCLUDE伪指令来包括包含文件(include file),用INCLUDELIB来包含库,就可以调用库中的函数,而不必查源代码。

使用字符串Working With Strings
WIN32中,定义字符串以十六进制“00”结尾。

也可以使用其它的格式,OLE用字节或是UNICODE双字节表示字符串,但多数API还是用“00”表示字符串结尾。

字符串可以按字节序列来处理,而不仅仅是字符数据。

以“00”结尾的字符串表示方法由来已久,它在汇编
中处理起来一样快速方便。

定义字符串:MyString db "this is a string",0
以前指令集有内建的字符串指令来处理字符串。

32位编程时,字符串使用源地址索引寄存器ESI,目的索引寄存器EDI和循环次数寄存器ECX来处理字符串,使用REP/ REPE/ REPNE 来控制循环,在条件成立时退出循环。

例如:
cld ; set direction flag forward
mov esi,source ; put address into the source index
mov edi,dest ; put address into the destination index
mov ecx,ln ; put the number of bytes to copy in ecx
; --------------------------------------------------
; repeat copying bytes from ESI to EDI until ecx = 0
; --------------------------------------------------
rep movsb
这个例子拷贝一段数据,不一定是字符串,当ECX自动减到0时退出循环。

也可以用lodsb 指令(将[ESI]装入al中)和stosb(将al放入[EDI]中)指令完成这个工作。

下面的例子处理“00”结尾的字符串:
mov esi,source ; put address into the source index
mov edi,dest ; put address into the destination index
label:
mov al,[esi] ; copy byte at address in esi to al
inc esi ; increment address in esi
mov [edi],al ; copy byte in al to address in edi
inc edi ; increment address in edi
cmp al,0 ; see if its an ascii zero
jne label ; jump back and read next byte if not
这个例子的源代码比较长,但比上个例子处理速度快的多,因为它利用处理器的功能指令成对工作(注:成对,pairing,相邻代码同时处理)。

这比不成对的指令当然快。

匿名标签Anonymous Lables
MASM中引用地址需要定义很多标签,标签越多,会引起不必要的麻烦。

在一段代码允许使用匿名标签来表示地址,符号是“@@:”。

用@F和@B表示引用,jmp @F;的意思是前向引用到最近的@@:,而jmp @B;表示向回跳,到最近的@@:。

使用宏Working With Macros
正确的使用宏可以简化代码,也可以简化编程过程。

宏在编译之前的预处理中完成,是一种转换约定。

写法是:
Do_This_Operation MACRO ;宏名
; write your code here
ENDM
宏可以有代码和参数(宏参数),下例定义颜色:
RGB MACRO red,green,blue
xor eax,eax
mov al,blue ; blue
rol eax,8
mov al,green ; green
rol eax,8
mov al,red ; red
ENDM
引用:
RGB 125,175,225
或RGB Byte1,Byte2,Byte3
或RGB cl,ch,dl
预处理时,遇到宏名就会将定义的宏在此处展开。

库模块的宏定义稍有不同,它有个name n子句(参见宏参考)。

宏内部定义了标签,并且有到标签的跳转,如果指令代码中调用宏多次,就会出现标签重复的错误。

MASM用局部标签的方式来解决这个问题。

宏中的标签用LOCAL定义,方式如下:
MyMacro MACRO Parameter1,Parameter2 etc ...
LOCAL MyLabel ;声明局部标签
; asm code
jmp MyLabel
; asm code
MyLabel:
; other asm code
ENDM
程序不同的地方调用宏,编译时会为宏内标签分配不同的数字,将标签转化为类似?00001:的形式,下一个调用转化成?00002:,按顺序来避免标签相同。

基本循环设计Basic Loop Design
设计循环的基本原则是速度快代码少。

如下例:
mov edx,1000000
label:
; assembler code here
dec edx
jnz label
edx减少到0时会将ZERO标志置位。

程序运行速度会很快。

Intel处理器的循环指令loop/ loope/ loopz/ loopnz/ loopne,都比cmp/jmp形成的代码慢的多。

不带有REP的字符串指令movs/ lods/ cmps/ scas也比手工写的代码慢一些,这些指令一般要指定字符串的大小。

手工写的代码是速度优化的选择,如JCXZ指令可以用test ecx,ecx和je XXXX来代替。

使用结构Working With Structures
进行Windows编程时,经常需要定义一块数据,并引用这个数据结构的句柄。

一般用“结构”来组织数据,用句柄(结构的地址)来定位这块数据为一个单元。

汇编中,结构由成员变量组成,如常用的RECT结构,拥有4个DWORD成员:
RECT STRUCT ;定义结构名
left DWORD ?;定义成员变量
top DWORD ?
right DWORD ?
bottom DWORD ?
RECT ENDS
注意每个成员的名字、数据尺寸和指定值。

用?表示成员没有用数值初始化。

结构是成员变量组成的内存片段,RECT结构由4个DWORD成员组成。

结构在定义时定位不同,成员变量的赋值方法也不同。

如结构分配于源程序代码的.DA TA段,它可以在初始化时直接赋值,但如果是在函数的堆栈中的LOCAL类型,就要用代码来赋值:
LOCAL Rct :RECT ;Rct为结构变量,RECT是上面定义的结构体
; code
mov Rct.left,1
mov Rct.top, 2
mov Rct.right,3
mov Rct.bottom,4
结构成员是一个内存操作数,不能接将另一个内存操作数直接MOV到成员变量中。

用Invoke调用函数时,结构可以做为单个的地址参数来使用,用法是前缀“ADDR”,形式:invoke APIcall,parameter1,parameter2,ADDR Rct
结构中的成员要写出名字,函数可以直接用成员名引用结构中的成员变量:
MyProc proc par1:DWORD,par2:DWORD,MyRect:RECT
mov eax,MyRect.left ; copy first member into EAX
调用上面的函数:invoke MyProc,par1,par2,Rct
32位Windows编程时经常有嵌套的结构,定义嵌套结构:
MyNestedStruct STRUCT
item1 RECT<>
item2 POINT <>
MyNestedStruct ENDS
其中RECT是前面定义的结构,POINT结构的定义如下:
POINT STRUCT
xDWORD ?
yDWORD ?
POINT ENDS
MyNestedStruct结构中总共有6个变量,4个是RECT结构的变量,2个是POINT结构的变量。

在椎栈中就是:LOCAL mns:MyNestedStruct;6个成员为:
mns.item1.left
mns.item1.top
mns.item1.right
mns.item1.bottom
mns.item2.x
mns.item2.y
结构可以多重嵌套,遵循上述规则。

结构的句柄一般是结构的地址,这在Windows代码设计时越来越普遍。

在汇编中,将RECT
结构的地址传递给函数:invoke MyFunction,ADDR Rct;程序代码的其它部分就应该有这样的函数定义:MyFuction proc lpRect:DWORD
RECT结构比较简单,得到成员变量地址很方便。

方法:将结构的地址放入寄存器,然后用偏移引用成员变量:
mov eax,lpRct
mov [eax],DWORD PTR 10
mov [eax+4],DWORD PTR 12
mov [eax+8],DWORD PTR 14
mov [eax+12],DWORD PTR 16
如果结构非常复杂,这样引用容易出错。

MASM汇编为引用成员地址提供了一种检查方法,使用ASSUME伪指令指定引用寄存器,然后引用:
ASSUME eax:PTR RECT
mov eax,lpRct
mov [eax].left,10
mov [eax].top,12
mov [eax].right,14
mov [eax].bottom,16
ASSUME eax:nothing
ASSUME伪指令向编译器指明EAX寄存器作为RECT结构指针,最后一行语句再告诉编译器寄存器不再充当RECT结构指针。

编译器会根据ASSUME伪指令检查EAX寄存器是否真正存贮了结构的地址(句柄)。

还有一种“类型预测”引用方式,也用到寄存器,但不用ASSUME 指令:
mov eax,lpRct
mov (RECT PTR [eax]).left,10
mov (RECT PTR [eax]).top,12
mov (RECT PTR [eax]).right,14
mov (RECT PTR [eax]).bottom,16
MASM对于结构引用的方式,优点在于不需要计算成员的偏移,直接使用成员变量名引用,编写程序方便可靠。

不可取之处是占用了寄存器,如果没有可用的寄存器,那就需要分配LOCAL类型的变量,将每个需要的结构成员数据拷贝到相应的变量中。

MASM Reference
MASM参考
讲指令或处理器部分将Argument译成“操作数”;讲宏部分将Argument译成“实体”或“数值”;Pairing译成“成对”;directive译成伪指令。

比较难办的是区分Argument自变量和Parameter参数。

经常用的是Argument,我们的习惯是,比如定义宏伪指令同一行中Macro 接下来的都可以叫做参数,而它在这里叫做Argument。

一般它在指令或过程中有实变量传递的地方才叫做参数Parameter,而用于伪指令或宏中的虚变量叫做Argument。

至于子过程和函数,没有详细甄别。

Microsoft asembler (MASM),是汇编语言的一种,版权完全归Microsoft所有。

微软公司的意图是让开发者致力于减少对于寄存器和硬件使用的关心,而专心编制程序,未偿不是一件好事——硬件的东西毕竟由Intel公司所有。

但很难想象一个汇编程序开发人员不去关心标志寄存器、内存模式或者I/O接口,逻辑上符合Microsoft思路的东西往往实际上并不好用,如同C++被微软包装以后,别人再不敢用一样——甚至他自己的软件都不用VC++来开发。

可以看出,这一版本的MASM(指6.1)的主攻方向是宏,微软好象要用宏在汇编再次引起一场革命。

我觉得优秀的汇编程序员不会有精力去关心新出台的这个宏规则或那个宏规则,他更关心的是一串串二进制码,如何以最优的方式控制CPU运作。

开发漂亮的界面或者几MB 的代码根本不是汇编的长项,程序员也不要在这方面浪费太多精力。

MASM也提供了足够多的访问Win32API控制操作系统的方式。

本书主旨是将MASM6.1帮助文档翻译整理,以便于阅读和参考,没有详细讲解指令或者编程技巧。

宏参考MACRO Reference
MASM宏汇编的好处之一是在阅读和编译代码之前提供了一个强有力的预处理文字平台,帮助汇编程序员开发出更好的速度快、可靠性更高的代码。

宏是程序员制造代码的秘密武器。

尤其是其中的Invoke语法,给调用API或其它函数提供了极大的方便,程序员编写代码扩展了相当的灵活性和创造力。

基本语法
宏定义:
语法:name MACRO [parameter[:tag]] [,parameter[:tag]]...] ;定义宏名和宏参数
[LOCAL varlist] ;定义宏内局部变量
statements
[EXITM [textitem]] ;如果超出块大小,就终止扩展。

返回嵌套层数。

ENDM ;结束宏定义
说明:编译器在代码中展开的宏代码叫做“展开码”(in source code)。

过程和函数宏可以嵌套40层,而文本宏可以嵌套20层。

宏可以在源代码任何地方重新定义,新定义的内容会在所
有子段(注:可能就是在后面的代码)中取代以前定义的内容。

定义函数的宏只有在没有被展开前可以被重新定义。

例子:
mymacro MACRO value:REQ,reg:=<AX>,options:V ARARG
LOCAL returnval
.....
EXITM returnval
ENDM
宏循环结构:
FOR循环
语法:FOR parameter[:REQ|:=default],<argument [,argument]...>
statements
ENDM
说明:每个Argument 重复一次宏体。

当前的变量(Argument)会取代Parameter的内容。

FORC循环
语法:FORC parameter,<string>
statements
ENDM
说明:同FOR循环,用于字符串字符循环。

GOTO循环
语法:GOTO macrolabel
说明:编译器会跳到宏标签。

只能在MACRO,FOR,FORC,REPEA T或WHILE块中使用,宏标签也只能在块中可见。

例子:
MACRO Instruments
IFNDEF C ymbal
GOTO Crash
ENDIF
-
:Crash
.ERR <Undefined symbol.>
-
ENDM
REPEA T循环:
语法:REPEA T expression
statements
ENDM
说明:按表达式的次数编译块。

Expression在第一次编译通过之前必须是数字常量。

WHILE循环:
语法:WHILE expression
statements
ENDM
说明:按表达式的次数编译块(直到表达式为False即0)。

Expression在编译时必须是数字常量。

IF条件宏:
语法:IF cond expression
ifstatements
[ELSEIF] cond expression
elseifstatements
-
[ELSE]
elsestatements
ENDIF
说明:根据常量或环境变量的不同,编译根据IF宏的条件可以在程序中产生不同的源代码。

如同一个源文件可以根据内存模式和操作环境产生不同代码。

IF宏可以嵌套20层,嵌套的IF宏不能在包含文件、结构、联合和宏定义中展开。

当ELSE前面所有的IF条件(cond)判断为假(false/zero)时,ELSE后面的汇编语句才能被激活并展开。

需要注意的是每个IF宏中只能有一个ESLE语句。

ELSE和ELSEIF语句是可选的。

IFE、IFB等宏的用法是类似的。

退出宏EXITM
EXITM [textitem]
一般用在循环结构的宏中,结束宏扩展。

如果在嵌套宏中使用,会返回上级宏。

参数是可选的返回值。

定义宏变量LOCAL
LOCAL localname [,localname]...
在宏内定义标签或符号(宏变量)。

编译过程中宏每一次展开都会赋予不同的内部名称。

&parameter&
用于宏或循环块中,将宏参数替换成文本。

只能用在宏参数两边、引号字符串中或尖括号中。

例:
Guard MACRO Prisonernum:REQ, Prisonername:=Anonymous
ECHO <Number &Prisonernum has arrived at &@Ti me.>
EXITM Prisonernum&&Prisonername
ENDM
Badge Guard 2
宏参数属性V ARARG (Macro)
将不定个数的量值传递给宏的时候,宏参数要使用V ARARG属性,使用一个宏参数就可以向宏传递若干个量。

只能将这个属性应用于宏定义的最后一个参数。

在宏定义中,FOR语句可以方便的取出宏参数中的每个量。

下例是用FOR语句计算宏参数中传递的量值个数,量值用计数位置来引用。

@ArgCount MACRO parmlist:V ARARG
LOCAL count
count = 0
FOR parm,<parmlist>
count = count + 1 ;Count the parameters
ENDM
EXITM count
ENDM
宏函数
宏函数ArgI
;* @ArgI - 宏返回一连串变量中的由number指定的某个变量的值
;* Shows: Directives - FOR LOCAL EXITM TEXTEQU =
;* Operator - EQ
;* 宏参数:index - 需要返回的变量的位置索引
;* arglist - 宏参数变量列表
@ArgI MACRO index:REQ,arglist:V ARARG
LOCAL count,retstr
count = 0
FOR arg,<arglist>
count = count + 1
IF count EQ index
retstr TEXTEQU <arg>
ENDIF
ENDM
EXITM retstr
ENDM
宏函数ArgRev
;* @ArgRev - 给一系列宏变量排序。

;* Shows: Operators - <> ! %
;* String directive - SUBSTR
;* Predefined function - @SizeStr
;* 参数:arglist - 需要排序的变量列表
@ArgRev MACRO arglist
LOCAL txt,arg
txt TEXTEQU <>
% FOR arg,arglist
txt CA TSTR <arg>,<!,>,txt
ENDM
txt SUBSTRtxt,1,@SizeStr( %txt ) - 1
txt CA TSTR<!<>,txt,<!>>
EXITM txt
ENDM
宏函数CA TSTAR
@CatStr(string[,string...])
name CA TSTR [textitem[,textitem...]]
连接字符串。

除使用表达式操作符%,宏不会扩展。

下面两句等同:
language TEXTEQU @CatStr (<Quick>,<Pascal>)
language CA TSTR <Quick>,<Pascal>
宏函数INSTR
@InStr([position],string1,string2)
name INSTR [position,] textitem1,textitem2
查找字符串,返回第一次匹配的位置。

查找从可选宏参数position指示的位置开始。

除非用表达式操作符%,宏不会扩展。

第一个字符位置为1。

没找到返回0。

下面两句等同:
develop TEXTEQU @InStr (2,<MikeBobMichaelJim>,<Mi>) ;develop is now 8 develop INSTR 2,<MikeBobMichaelJim>,<Mi> ;develop is now 8
宏函数SIZESTR
@SizeStr(string)。

相关文档
最新文档