《汇编语言:基于X86处理器》第10章 结构和宏(3)
前面的章节讲的都是指令和逻辑,本章咱们介绍一个新的概念。在C、C++、Java等高级高阶语言中结构体和宏是很重要的编程模块,汇编语言也有结构体和宏的部分,接下来本章讲讲结构和宏。
10.3条件汇编伪指令
很多不同的条件汇编伪指令都可以和宏一起使用,这使得宏更加灵活。条件汇编伪指令常用语法如下所示:
IF conditionstatements[ELSEstatements]
ENDIF
提示 本章给出的常量伪指令不应与6.7节介绍的运行时伪指令混淆,如.IF和.ENDIF等。后者按照运行时的寄存器与变量值来计算表达式。
表10-3列出了更多常用的条件汇编伪指令。若说明为该伪指令允许汇编,就意味着所有的后续语句都将被汇编,直到遇到下一个ELSE或ENDIF伪指令。必须强调的是,表中列出的伪指令是在汇编时而不是运行时计算。
10.3.1 检查缺失的参数
宏能够检查其参数是否为空。通常,宏若接收到空参数,则预处理程序在进行宏展开时会导致出现无效指令。例如,如果调用宏mWrtieString却又不传递实参,那么宏展开在把字符串偏移量传递给 EDX 时,就会出现无效指令。汇编器生成的如下语句检测出缺失的操作数,并产生了一个错误消息:
mWriteString
1 push edx
1 mov edx, OFFSET
Macro2.asm(18):error A2081:missing operand after unary operator
1 call WriteString
1 pop edx
为了防止由于操作数缺失而导致的错误,可以使用IFB(ifblank)伪指令,若宏实参为空,则该伪指令返回值为真。还可以使用IFNB(ifnotbank)运算符,若宏实参不为空,则其返回值为真。现在编写mWrtieString,的另一个版本,使其可以在汇编时显示错误消息:
mWriteString MACRO stringIFB <string>ECHO -----------------------------------------ECHO * Error:parameter missing in mWriteStringECHO * (no code generated)ECHO -----------------------------------------EXITMENDIFpush edxmov edx, OFFSET stringcall WriteStringpop edx
ENDM
(回忆一下10.2.2节,程序汇编时,ECHO伪指令向控制台写一个消息。)EXITM 伪指令告诉预处理程序退出宏,不再展开更多宏语句。汇编的程序有缺失参数时,其屏幕输出如下所示:
VS2019编译时,只输出一条错误信息???
10.3.2 默认参数初始值设定
宏可以有默认参数初始值。如果调用宏出现了宏参数缺失,那么就可以使用默认参数。其语法如下:
paramname :<arqument>
(运算符前后的空格是可选的。)比如,宏mWriteln提供含有一个空格的字符串作为其默认参数。如果对其进行无参数调用,它仍然会打印一个空格并换行:
mWriteln MACRO text:=<" ">mWrite textcall Crlf
ENDM
若把空字符串("")作为默认参数,那么汇编器会产生错误,因此必须在引号之间至少插入一个空格。
10.3.3 布尔表达式
汇编器允许在包含FF和其他条件伪指令的常量布尔表达式中使用下列关系运算符
LT 小于
GT 大于
EO 等于
NE 不等于
LE 小于等于
GE 大于等于
10.3.4 IF、ELSE和ENDIF 伪指令
IF伪指令的后面必须跟一个常量布尔表达式。该表达式可以包含整数常量、符号常量或者常量宏实参,但不能包含寄存器或变量名。仅适用于IF 和ENDIF 的语法格式如下:
IF expressionstatement-list
ENDIF
另一种格式则适用于IF、ELSE和ENDIF:
IF expressionstatement-list
ELSEstatement-list
ENDIF
示例: 宏mGotoxyConst宏mGotoxyConst利用LT和GT运算符对传递给宏的参数进行范围检查。实参X和Y必须为常数。还有一个常数符号ERRS对发现的错误进行计数。根据 X的值,可以将ERRS设置为1。根据Y的值,可以将 ERRS 加 1。最后,如果 ERRS大于零,EXITM 伪指令退出宏:
;10.3.4.asm 10.3.4 IF、ELSE和ENDIF 伪指令INCLUDE Irvine32.inc
;-----------------------------------------------
;将光标位置设置在X列Y行。
;要求X和丫的坐标为常量表达式
;其范围为0≤X<80,0≤Y<25。
;-----------------------------------------------
mGotoxyConst MACRO X:REQ, Y:REQLOCAL ERRS ;;局部常量ERRS = 0IF (X LT 0) OR (X GT 79)ECHO Warning: First argument to mGotoxy (X) is out of range.ECHO *******************************************************ERRS = 1ENDIFIF (Y LT 0) OR (Y GT 24)ECHO Warning: Second argument to mGotoxy (Y) is out of range.ECHO *******************************************************ERRS = ERRS + 1ENDIFIF ERRS GT 0 ;;若发现错误 ECHO Error: find error ERRS = (ERRS)EXITM ;;退出宏ENDIFpush edxmov dh, Ymov dl, Xcall Gotoxypop edx
ENDM.data
str1 BYTE "Please enter your name: ", 0 .code
main PROCmov ebx, OFFSET str1mGotoxyConst 12,12mGotoxyConst 80,25call CrlfINVOKE ExitProcess, 0
main ENDP
END main
编译:
10.3.5 IFIDN和IFIDNI伪指令
FIDNI 伪指令在两个符号(包括宏参数名)之间进行不区分大小写的比较,如果它们相等,则返回真。IFIDN 伪指令执行的是区分大小写的比较。如果想要确认宏主调者使用的寄存器参数不会与宏内使用的寄存器发生冲突,那么可以使用这两个伪指令中的前者。IFIDNI的语法如下:
IFIDNI <symbol>, <symbol>statements
ENDIF
IFIDN 的语法与之相同。例如下面的宏mReadBuf,其第二个参数不能用EDX,因为当buffer 的偏移量被送入 EDX 时,原来的值就会被覆盖。在如下修改过的宏代码中,如果这个条件不满足,就会显示一条警告消息:
;10.3.5.asm 10.3.5 IFIDN和IFIDNI伪指令
;FIDNI 伪指令在两个符号(包括宏参数名)之间进行不区分大小写的比较,
;如果它们相等,则返回真。IFIDN 伪指令执行的是区分大小写的比较。
;IFIDN和IFIDNI伪指令只在编译时起作用,并不会嵌入到生成的代码中。INCLUDE Irvine32.inc
;-----------------------------------------------
;将键盘输入读到缓冲区。
;接收:缓冲区偏移量,最多可输入字符的数量。第二个参数不能用edx或EDX。
;-----------------------------------------------
mReadBuf MACRO bufferPtr, maxCharsIFIDNI <maxChars>, <EDX>ECHO Warning:Second arqument to mReadBuf cannot be EDX.ECHO **************************************************EXITM ;;退出宏ENDIFpush ecxpush edxmov edx, bufferPtrmov ecx, maxCharscall ReadStringpop edxpop ecx
ENDM.data
buffer BYTE 31 DUP(?), 0 .code
main PROCmov ebx, OFFSET buffermReadBuf OFFSET buffer, ecxmReadBuf OFFSET buffer, edxcall CrlfINVOKE ExitProcess, 0
main ENDP
END main
下面的语句将会导致宏产生警告消息,因为 EDX 是其第二个参数:
10.3.6 示例:矩阵行求和
9.4.2 节展示了如何计算字节矩阵中单个行的总和。第9章编程练习要求将其扩展到字矩阵和双字矩阵。尽管这个解决方案有些冗长,现在还是要看看能否用宏来简化任务。首先,给出第9章的原始cale_row_sum 过程:
;-------------------------------------------------
;计算字节矩阵中一行的和数。
;接收:EBX=表偏移量,EAX=行索引
;;ECX=按字节计的行大小。
;返回:EAX为和数。
;-------------------------------------------------
calc_row_sum PROC USES ebx ecx edx esimul ecx ;行索引 x 行大小add ebx, eax ;行偏移量mov eax, 0 ;累加器mov esi, 0 ;列索引
L1: movzx edx, BYTE PTR[ebx+esi] ;取一个字节add eax, edx ;与累加器相加inc esi ;行中的下一个字节loop L1 ret
calc_row_sum ENDP
从把 PROC 改为MACRO 开始,删除 RET 指令,把 ENDP 改为ENDM。由于没有宏与USES 伪指令功能相当,因此插人 PUSH 和 POP 指令:
mCalc_row_sum MACROpush ebx ;保存被修改的寄存器push ecxpush esimul ecx ;行索引 x 行大小add ebx, eax ;行偏移量mov eax, 0 ;累加器mov esi, 0 ;列索引
L1: movzx edx, BYTE PTR[ebx+esi] ;取一个字节add eax, edx ;与累加器相加inc esi ;行中的下一个字节loop L1 pop esi ;恢复被修改的寄存器pop ecxpop ebx
ENDM
接着,用宏参数代替寄存器参数,并对宏内寄存器进行初始化
mCalc_row_sum MACRO index, arrayOffset, rowSizepush ebx ;保存被修改的寄存器push ecxpush esi;设置需要的寄存器mov eax, indexmov ebx, arrayOffsetmov ecx, rowSizemul ecx ;行索引 x 行大小add ebx, eax ;行偏移量mov eax, 0 ;累加器mov esi, 0 ;列索引
L1: movzx edx, BYTE PTR[ebx+esi] ;取一个字节add eax, edx ;与累加器相加inc esi ;行中的下一个字节loop L1 pop esi ;恢复被修改的寄存器pop ecxpop ebx
ENDM
然后,添加一个参数 eltType指定数组类型(BYTE、WORD或DWORD);
mCalc_row_sum MACRO index, arrayoffset rowSize, eltType
复制到ECX的参数rowSize现在表示的是每行的字节数。如果要用其作为循环计数器,那么它就必须转换为每行的元素(element)个数。因此,若为 16 位数组,就将 ECX 除以2;若为双字数组,就将ECX 除以4。实现上述操作的快捷方式为:eltType 除以2,把商作为移位计数器,再将 ECX 右移:
shr ecx, (TYPE eltType /2) ;byte=0,word=1,dword=2
TYPE eltType 就成为MOVZX指令中基址-变址操作数的比例因子:
movzx edx, eltType PTR[ebx+ esi*(TYPE eltType)]
若 MOVZX 右操作数为双字,那么指令不会汇编。所以,当 eltType 为 DWORD 时,需要用IFIDNI运算符另外编写一条MOV指令:
IFIDNI <eltType>,<DWORD>mov edx, eltType PTR[ebx+esi*(TYPE eltType)]ELSEmovzx edx, eltType PTR[ebx,esi*(TYPE eltType)]ENDIF
最后必须结束宏,记住要把标号L1指定为LOCAL:
下面用字节数组、字数组和双字数组对宏进行示例调用。
;10.3.6_rowsum.asm 10.3.6 示例:矩阵行求和
;计算数组行之和,宏扩展INCLUDE Irvine32.inc
;-------------------------------------------------
;计算字节矩阵中一行的和数。
;接收:EBX=表偏移量,EAX=行索引
;;ECX=按字节计的行大小。
;返回:EAX为和数。
;-------------------------------------------------
mCalc_row_sum MACRO index, arrayOffset, rowSize, eltType
LOCAL L1push ebx ;保存被修改的寄存器push ecxpush esi;设置需要的寄存器mov eax, indexmov ebx, arrayOffsetmov ecx, rowSize;计算行偏移量mul ecx ;行索引 x 行大小add ebx, eax ;行偏移量;初始化循环计数器shr ecx,(TYPE eltType/2) ;byte=0,word=1,dword=2;初始化累加器和列索引mov eax, 0 ;累加器mov esi, 0 ;列索引
L1: IFIDNI <eltType>,<DWORD>mov edx, eltType PTR[ebx+esi*(TYPE eltType)]ELSEmovzx edx, eltType PTR[ebx+esi*(TYPE eltType)]ENDIF;;movzx edx, BYTE PTR[ebx+esi] ;取一个字节add eax, edx ;与累加器相加inc esi ;行中的下一个字节loop L1 pop esi ;恢复被修改的寄存器pop ecxpop ebx
ENDM.data
tableB BYTE 10h,20h,30h,40h,50h
RowSizeB = ($-tableB)BYTE 60h, 70h, 80h, 90h, 0A0hBYTE 0B0h, 0C0h, 0D0h, 0E0h, 0F0h
tableW WORD 10h, 20h, 30h, 40h, 50h
RowsizeW = ($ - tableW)WORD 60h, 70h, 80h, 90h, 0A0hWORD 0B0h, 0C0h, 0D0h, 0E0h, 0F0h
tableD DWORD 1020h,3040h,5060h,7080h,90A0h
RowsizeD = ($ - tableD)DWORD 60h, 70h, 80h, 90h, 0A0hDWORD 0B0h, 0C0h, 0D0h, 0E0h, 0F0h
index DWORD 0.code
main PROCmCalc_row_sum index, OFFSET tableB, RowSizeB, BYTEcall WriteHexcall Crlfmov index, 1mCalc_row_sum index, OFFSET tableW, RowSizeW, WORDcall WriteHexcall Crlfmov index, 2mCalc_row_sum index, OFFSET tableD, RowSizeD, DWORDcall WriteHexcall CrlfINVOKE ExitProcess, 0
main ENDP
END main
运行调试:
10.3.7 特殊运算符
下述四个汇编运算符使得宏更加灵活:
1.替换运算符(&)
替换运算符(&)解析对宏参数名的有歧义的引用。宏mShowRegister(10.2.5节)显示了一个32位寄存器的名称和十六进制的内容。示例调用如下:
.code
mShowRegister ECX
下面是调用mShowRegister产生的示例输出:
ECX=00000101
在宏内可以定义包含寄存器名的字符串变量:
mShowRegister MACRO regName
.data
tempStr BYTE " regName=", 0
但是预处理程序会认为regName是字符串文本的一部分,因此,不会将其替换为传递给宏的实参值。相反,如果添加了&运算符,它就会强制预处理程序在字符串文本中插入宏实参(如ECX)。下面展示的是如何定义tempStr:
mShowRegister MACRO regName
.data
tempStr BYTE " ®Name=", 0
2.展开运算符(%)
展开算符(%)展开文本宏并将常量表达式转换为文本形式。有几种方法实现该功能。若使用的是 TEXTEQU,%运算符就计算常量表达式,再把结果转换为整数。在下面的例子中,%运算符计算表达式(5+count),并回整数15(以文本形式);
count = 10
sumVal TEXTEQU %(5+count) ;="15"
如果宏请求的实参是整数常量,%运算符就能使程序具有传递一个整数表达式的灵活性。计算这个表达式得到结果值,然后将这个值传递给宏。例如,调用 mGotoxyConst 时,计算表达式的结果分别为50和7:
mGotoxyConst %(5*10),%(3+4)
预处理程序将产生如下语句:
1 push edx
1 mov dh, 7
1 mov dl, 50
1 call Gotoxy
1 pop edx
%在一行的首位 当展开运算符(%)是一行源代码的第一个字符时,它指示预处理程序展开该行上的所有文本宏和宏函数。比如,假设想在汇编时将数组大小显示在屏幕上。下面的尝试不会产生期望的结果:
.data
array DWORD 1,2,3,4,5,6,7,8
.code
ECHO The array contains (SIZEOF array) bytes
ECHO The array contains %(SIZEOF array) bytes
屏幕输出没什么用:
反之,如果用TEXTEQU编写包含(SIZEOF array)的文本宏,那么该宏就可以展开为之后的代码行:
TempStr TEXTEQU %(SIZEOF array)
% ECHO The array contains TempStr bytes
产生的输出如下所示:
The array contains 32 bytes
显示行号 下面的宏Mul32将它前两个实参相乘,乘积由第三个实参返回。其形参可以是寄存器、内存操作数和立即数(乘积除外):
;showline.asm 10.3.7 特殊运算符
;显示行号 下面的宏Mul32将它前两个实参相乘,乘积由第三个实参返回。
;其形参可以是寄存器、内存操作数和立即数(乘积除外):INCLUDE Irvine32.incMUL32 MACRO op1, op2, productIFIDNI <op2>,<EAX>LINENUM TEXTEQU %(@LINE)ECHO ------------------------------------------------
% ECHO * Error: on line LINENUM:EAX cannot be the secondECHO * argument when invoking the NUM32 macro.ECHO -------------------------------------------------EXITMENDIFpush eaxmov eax, op1mul op2mov product, eaxpop eax
ENDM.data
val1 DWORD 1234h
val2 DWORD 1000h
val3 DWORD ?
array DWORD 1,2,3,4,5,6,7,8.code
main PROCMUL32 val1, val2, val3mov eax, val2MUL32 val1, EAX, val3call CrlfINVOKE ExitProcess, 0
main ENDP
END main
Mul32要检查的一个重要要求是:EAX不能作为第二个实参。这个宏有趣的地方是,它显示的是其调用者的行号,这样更加易于追踪并解决问题。首先定义文本宏LINENUM.它引用的 @LINE 是一个预先定义的汇编运算符,其功能为返回当前源代码行的编号:
LINENUM TEXTEQU %(%LINE)
接着,在含有ECHO语句的代码行第一列上的展开运算符(%)使得LINENUM 被展开:
% ECHO *Error on line LINENUM:EAX cannot be the second
假设如下宏调用发生在程序的40行:
MUL32 val1, eax, val3
那么,汇编时将显示如下信息:
在Macro3.asm程序中可以查看Mul32的测试
3.文字文本运算符(<>)
文字文本 (literal-text)运算符(<>)把一个或多个字符和符号组合成一个文字文本,以防止预处理程序把列表中的成员解释为独立的参数。在字符串含有特殊字符时该运算符非常有用,比如逗号、百分号(%)、和号(&)以及分号(;),这些符号既可以被解释为分隔符又可以被解释为其他的运算符。例如,本章之前给出的宏mWrite接收一个字符串文本作为其唯一的实参。如果传递的字符串如下所示,预处理程序就会将其解释为3个独立的实参:
mwrite "Line three": 0dh, 0ah
第一个逗号后面的文本会被丢弃,因为宏只需要一个实参。然而,如果用文字文本运算符将字符串括起来,那么预处理程序就会把尖括号内所有的文本当作一个宏实参:
mwrite <"Line three",0dh,0ah>
完整代码测试笔记
;10.3.7_3.asm 10.3.7 特殊运算符
;文字文本 (literal-text)运算符(<>)把一个或多个字符和符号组合成一个文字文本,
;以防止预处理程序把列表中的成员解释为独立的参数。INCLUDE Irvine32.incmWrite MACRO textLOCAL string ;;local标号.data ;;定义字符串string BYTE text, 0.codepush edxmov edx, OFFSET stringcall WriteStringpop edx
ENDM.data
array BYTE "hello world", 0.code
main PROCmWrite "Line three", 42h, 43hcall CrlfmWrite <"Line three", 42h, 43h>INVOKE ExitProcess, 0
main ENDP
END main
编译的时候会提示警告:
运行调试:
4.文字字符运算符(!)
构造文字字符(literal-character)运算符(!)的目的与文字文本运算符的几乎完全一样:强制预处理程序把预先定义的运算符当作普通的字符。在下面的 TEXTEQU 定义中,运算符!可以防止符号>被当作文本分隔符:
BadYValue TEXTEQU <Warning: Y-coordinate is !>24>
警告信息示例 下面的例子有助于说明运算符%、& 和!是如何工作的。假设已经定义了符号BadYValue。现在创建一个宏 ShowWarning,接收一个用引号括起来的文本实参,并将其传递给宏mWrite。注意替换(&)运算符的用法:
ShowWarning MACRO messagemWrite "&message"
ENDM
然后调用ShowWarning,把表达式%BadYValue传递给它。%运算符计算(解析)BadYValue,并生成与之等价的字符串:
.code
ShowWarning %BadYValue
正如所期望的,程序运行并显示警告信息:
Warning: Y-coordinate is > 24
完整代码测试笔记
;10.3.7_4.asm 10.3.7 特殊运算符
;4.文字字符运算符(!)
;警告信息示例 下面的例子有助于说明运算符%、& 和!是如何工作的。INCLUDE Irvine32.incmWrite MACRO textLOCAL string ;;local标号.data ;;定义字符串string BYTE text, 0.codepush edxmov edx, OFFSET stringcall WriteStringpop edx
ENDMShowWarning MACRO messagemWrite "&message"
ENDM.data
array BYTE "hello world", 0
BadYValue TEXTEQU <Warning: Y-coordinate is !> 24>.code
main PROCmWrite "Line three", 42h, 43hcall CrlfmWrite <"Line three", 42h, 43h>call CrlfShowWarning %BadYValuecall CrlfINVOKE ExitProcess, 0
main ENDP
END main
运行调试:
10.3.8 宏函数
宏函数与宏过程有相似的地方,它也为汇编语言语句列表分配一个名称。不同的地方在于,宏函数通过EXITM 伪指令总是返回一个常量(整数或字符串)。如下例所示,如果给定符号已定义,则宏 IsDefined 返回真(-1);否则返回假(0):
IsDefined MACRO symbolIFDEF symbolEXITM <-1> ;;真ELSEEXITM <0> ;;假ENDIF
ENDM
EXITM(退出宏)伪指令终止了所有后续的宏展开。
调用宏函数 调用宏函数时,它的实参列表必须用括号括起来。比如,调用宏IsDefined并传递RealMode(一个可能已定义也可能还未定义的符号名);
IF IsDefined(RealMode)mov ax, @datamov ds,ax
ENDIF
如果在汇编过程中,汇编器在此之前已经遇到过对 RealMode 的定义,那么它就会汇编这两条指令:
mov ax, @data
mov ds, ax
同样的IF 伪指令可以被放在名为Startup的宏内:
Startup MACROIF IsDefined(RealMode)mov ax, @datamov ds, axENDIF
ENDM
像 IsDefined 这样的宏可以用于设计多种内存模式的程序。比如,可以用它来决定使用哪种头文件:
IF IsDefined(RealMode)INCLUDE Irvine16.inc
ELSEINCLUDE Irvine32.inc
ENDIF
定义 RealMode符号 剩下的任务就只是找到定义RealMode 符号的方法。方法之一是把下面的代码行放在程序开始的位置:
RealMode=1
或者,汇编器命令行也有选项来定义符号,即,使用-D。下面的ML命令行定义了RealMode 符号并为其赋值1:
ML -C -DRealMode=1 myProg.asm
而保护模式程序中相应的ML命令就没有定义RealMode符号:
ML -c myProg.asm
HelloNew 程序 下面的程序(HelloNew.asm)使用刚才介绍的宏,在屏幕上显示了一条消息:
完整代码测试笔记
;10.3.8_HelloNew.asm 10.3.8 宏函数
;下面的程序(HelloNew.asm)使用刚才介绍的宏,在屏幕上显示了一条消息:INCLUDE Macros.inc
;宏函数
IF IsDefined(RealMode)INCLUDE Irvine16.inc
ELSEINCLUDE Irvine32.inc
ENDIFStartup MACROIF IsDefined(RealMode)mov ax, @datamov ds, axENDIF
ENDMmWrite MACRO textLOCAL string ;;local标号.data ;;定义字符串string BYTE text, 0.codepush edxmov edx, OFFSET stringcall WriteStringpop edx
ENDM.data
array DWORD 1000h, 2000h, 3000h, 4000h.code
main PROCStartupmWrite <"This program can be assembled to run ", 0dh, 0ah>mWrite <"in both Real mode and Protected mode. ", 0dh, 0ah>call CrlfINVOKE ExitProcess, 0
main ENDP
END main
运行调试:
第14~17章介绍了实模式编程。16 位实模式程序运行于模拟的MS-DOS 环境中,使用的是 Irvine16.inc 头文件和Irvine16链接库。
10.3.9 本节回顾
1.IFB伪指令的作用是什么?
答:IFB伪指令用于检查空宏参数。
2.IFIDN伪指令的作用是什么?
答:IFIDN伪指令比较两个文本值,若两者相等,则返回真。其执行的比较需要区分大小写。
3.哪条伪指令能停止所有后续的宏展开?
答:EXITM
4.IFIDNI与IFIDN 有什么不同?
答:IFIDNI与IFIDN相同,但不区分大小写。
5.IFDEF伪指令的作用是什么?
答:若符号已定义,则IFDEF返回真。
10.4 定义重复语句块
MASM 有许多循环伪指令用于生成重复的语句块:WHILE、REPEAT、FOR 和FORC。与LOOP 指令不同,这些伪指令只在汇编时起作用,并使用常量值作为循环条件和计数器:
●WHILE 伪指令根据一个布尔表达式来重复语句块。
●REPEAT 伪指令根据计数器的值来重复语句块。
●FOR 伪指令通过遍历符号列表来重复语句块。
●FORC伪指令通过遍历字符串来重复语句块。
示例程序Repeat.asm演示了上述每一条伪指令。
10.4.1 WHILE 伪指令
WHILE伪指令重复一个语句块,直到特定的常量表达式为真。其语法如下:
WHILE constExpressionstatements
ENDM
下面的代码展示了如何在1到F000 0000h之间生成斐波那契(Fibonacci)数,作为汇编时常数序列:
.data
val1 = 1
val2 = 1
DWORD val1 ;前两个值
DWORD val2
val3 = val1 + val2
WHILE val3 LT 0F0000000hDWORD val3val1 = val2val2 = val3val3 = val1+val2
ENDM
完整代码测试笔记
;10.4.1.asm 10.4.1 WHILE 伪指令
;下面的代码展示了如何在1到F000 0000h之间生成斐波那契(Fibonacci)数,作为汇编时常数序列:;.386
;.model flat, stdcall
;.stack 4096
;ExitProcess PROTO,dwExitCode:DWORDINCLUDE Irvine32.inc.data
array DWORD 11111111h, 22222222h, 33333333h, 44444444h
;定义一个标签以便引用这个数组
FibonacciArray LABEL DWORD
val1 = 1
val2 = 1
DWORD val1 ;前两个值
DWORD val2
val3 = val1 + val2
WHILE val3 LT 0F0000000hDWORD val3val1 = val2val2 = val3val3 = val1+val2
ENDM
;获取数组长度:
FibonacciCount = ($ - FibonacciArray) / 4.code
main PROCmov eax, val1mov ebx, val2mov esi, OFFSET array;示例1:获取第一个斐波那契数mov eax, [FibonacciArray];示例2: 获取第5个斐波那契数(从0开始计数)mov ebx, [FibonacciArray + 4*4];示例3: 循环遍历所有斐波那契数mov esi, OFFSET FibonacciArray ; 指向数组开始mov ecx, FibonacciCount ; 数组元素个数jecxz NoElements ; 如果数组为空则跳过ProcessArray:mov eax, [esi] ; 获取当前斐波那契数;打印斐波那契数call WriteDeccall Crlfadd esi, 4 ; 移动到下一个元素loop ProcessArrayNoElements: INVOKE ExitProcess, 0
main ENDP
END main
在编译的时候,宏会扩展生成斐波那契数数,此代码生成的数值可以在清单(LST)文件中查看。
00000000 .data00000000 11111111 array DWORD 11111111h, 22222222h, 33333333h, 44444444h222222223333333344444444;定义一个标签以便引用这个数组00000010 FibonacciArray LABEL DWORD= 00000001 val1 = 1= 00000001 val2 = 100000010 00000001 DWORD val1 ;前两个值00000014 00000001 DWORD val2= 00000002 val3 = val1 + val2WHILE val3 LT 0F0000000hDWORD val3val1 = val2val2 = val3val3 = val1+val2ENDM00000018 00000002 1 DWORD val30000001C 00000003 1 DWORD val300000020 00000005 1 DWORD val300000024 00000008 1 DWORD val300000028 0000000D 1 DWORD val30000002C 00000015 1 DWORD val300000030 00000022 1 DWORD val300000034 00000037 1 DWORD val300000038 00000059 1 DWORD val30000003C 00000090 1 DWORD val300000040 000000E9 1 DWORD val300000044 00000179 1 DWORD val300000048 00000262 1 DWORD val30000004C 000003DB 1 DWORD val300000050 0000063D 1 DWORD val300000054 00000A18 1 DWORD val300000058 00001055 1 DWORD val30000005C 00001A6D 1 DWORD val300000060 00002AC2 1 DWORD val300000064 0000452F 1 DWORD val300000068 00006FF1 1 DWORD val30000006C 0000B520 1 DWORD val300000070 00012511 1 DWORD val300000074 0001DA31 1 DWORD val300000078 0002FF42 1 DWORD val30000007C 0004D973 1 DWORD val300000080 0007D8B5 1 DWORD val300000084 000CB228 1 DWORD val300000088 00148ADD 1 DWORD val30000008C 00213D05 1 DWORD val300000090 0035C7E2 1 DWORD val300000094 005704E7 1 DWORD val300000098 008CCCC9 1 DWORD val30000009C 00E3D1B0 1 DWORD val3000000A0 01709E79 1 DWORD val3000000A4 02547029 1 DWORD val3000000A8 03C50EA2 1 DWORD val3000000AC 06197ECB 1 DWORD val3000000B0 09DE8D6D 1 DWORD val3000000B4 0FF80C38 1 DWORD val3000000B8 19D699A5 1 DWORD val3000000BC 29CEA5DD 1 DWORD val3000000C0 43A53F82 1 DWORD val3000000C4 6D73E55F 1 DWORD val3000000C8 B11924E1 1 DWORD val3000000CC ;获取数组长度:= 0000002F FibonacciCount = ($ - FibonacciArray) / 400000000 .code 00000000 main PROC00000000 B8 6D73E55F mov eax, val100000005 BB B11924E1 mov ebx, val20000000A BE 00000000 R mov esi, OFFSET array;示例1: 获取第一个斐波那契数0000000F A1 00000010 R mov eax, [FibonacciArray];示例2: 获取第5个斐波那契数(从0开始计数)00000014 8B 1D 00000020 R mov ebx, [FibonacciArray + 4*4];示例3: 循环遍历所有斐波那契数0000001A BE 00000010 R mov esi, OFFSET FibonacciArray ; 指向数组开始0000001F B9 0000002F mov ecx, FibonacciCount ; 数组元素个数00000024 E3 11 jecxz NoElements ; 如果数组为空则跳过00000026 ProcessArray:00000026 8B 06 mov eax, [esi] ; 获取当前斐波那契数;打印斐波那契数00000028 E8 00000000 E call WriteDec0000002D E8 00000000 E call Crlf00000032 83 C6 04 add esi, 4 ; 移动到下一个元素00000035 E2 EF loop ProcessArray00000037 NoElements: INVOKE ExitProcess, 000000037 6A 00 * push +000000000h00000039 E8 00000000 E * call ExitProcess0000003E main ENDP END main
生成的斐波那契数组,因此咱们调用时,只需要找到对应的起始地址,然后通过偏移量来访问对应的斐波那契数。
运行调试:
10.4.2 REPEAT 伪指令
在汇编时,REPEAT 伪指令将一个语句块重复固定次数。其语法如下:
REPEAT constExpressionstatements
ENDM
constExpression 是一个无符号整数常量表达式,用于确定重复次数。在创建数组时,REPEAT 的用法与 DUP类似。在下面的例子中,WeatherReadings 结构含有一个地点字符串和一个包含了降雨量与湿度读数的数组:
WEEKS_PER_YEAR = 52
WeatherReadings STRUCTlocation BYTE 50 DUP(0)REPEAT WEEKS_PER_YEARLOCAL rainfall, humidityrainfall DWORD ?humidity DWORD ?ENDM
WeatherReadings ENDS
由于汇编时循环会对降雨量和湿度重定义,使用 LOCAL 伪指令可以避免因其导致的错误。
完整代码测试笔记
;10.4.2_repeat.asm 10.4.2 REPEAT 伪指令
;REPEAT 的用法与 DUP类似。在下面的例子中,
;WeatherReadings 结构含有一个地点字符串和一个包含了降雨量与湿度读数的数组:;.386
;.model flat, stdcall
;.stack 4096
;ExitProcess PROTO,dwExitCode:DWORDINCLUDE Irvine32.incWEEKS_PER_YEAR = 52
WeatherReadings STRUCTlocation BYTE 50 DUP(0) ;50个字节;总共大小 = 50 + (52 * 8) = 466字节REPEAT WEEKS_PER_YEARLOCAL rainfall, humidityrainfall DWORD 22222222hhumidity DWORD 33333333hENDM
WeatherReadings ENDS.data
array DWORD 11111111h, 22222222h, 33333333h, 44444444h
weatherData WeatherReadings <>.code
main PROCmov esi, OFFSET weatherDataadd esi, 50 ;前面50个字符是locationmov ecx, 52mov edi, 0
L1: mov eax, [esi + edi*4] ;访问rainfall字段mov ebx, [esi + edi*4 + 4] ;访问humidity字段inc ediloop L1INVOKE ExitProcess, 0
main ENDP
END main
查.lst文件中查看REPEAT扩展结构:
= 00000034 WEEKS_PER_YEAR = 52000001D2 WeatherReadings STRUCT00000000 00000032 [ location BYTE 50 DUP(0) ;50个字节00];总共大小 = 50 + (52 * 8) = 466字节REPEAT WEEKS_PER_YEARLOCAL rainfall, humidityrainfall DWORD 22222222hhumidity DWORD 33333333hENDM00000032 22222222 1 ??0000 DWORD 22222222h00000036 33333333 1 ??0001 DWORD 33333333h0000003A 22222222 1 ??0002 DWORD 22222222h0000003E 33333333 1 ??0003 DWORD 33333333h00000042 22222222 1 ??0004 DWORD 22222222h00000046 33333333 1 ??0005 DWORD 33333333h0000004A 22222222 1 ??0006 DWORD 22222222h0000004E 33333333 1 ??0007 DWORD 33333333h00000052 22222222 1 ??0008 DWORD 22222222h00000056 33333333 1 ??0009 DWORD 33333333h0000005A 22222222 1 ??000A DWORD 22222222h0000005E 33333333 1 ??000B DWORD 33333333h00000062 22222222 1 ??000C DWORD 22222222h00000066 33333333 1 ??000D DWORD 33333333h0000006A 22222222 1 ??000E DWORD 22222222h0000006E 33333333 1 ??000F DWORD 33333333h00000072 22222222 1 ??0010 DWORD 22222222h00000076 33333333 1 ??0011 DWORD 33333333h0000007A 22222222 1 ??0012 DWORD 22222222h0000007E 33333333 1 ??0013 DWORD 33333333h00000082 22222222 1 ??0014 DWORD 22222222h00000086 33333333 1 ??0015 DWORD 33333333h0000008A 22222222 1 ??0016 DWORD 22222222h0000008E 33333333 1 ??0017 DWORD 33333333h00000092 22222222 1 ??0018 DWORD 22222222h00000096 33333333 1 ??0019 DWORD 33333333h0000009A 22222222 1 ??001A DWORD 22222222h0000009E 33333333 1 ??001B DWORD 33333333h000000A2 22222222 1 ??001C DWORD 22222222h000000A6 33333333 1 ??001D DWORD 33333333h000000AA 22222222 1 ??001E DWORD 22222222h000000AE 33333333 1 ??001F DWORD 33333333h000000B2 22222222 1 ??0020 DWORD 22222222h000000B6 33333333 1 ??0021 DWORD 33333333h000000BA 22222222 1 ??0022 DWORD 22222222h000000BE 33333333 1 ??0023 DWORD 33333333h000000C2 22222222 1 ??0024 DWORD 22222222h000000C6 33333333 1 ??0025 DWORD 33333333h000000CA 22222222 1 ??0026 DWORD 22222222h000000CE 33333333 1 ??0027 DWORD 33333333h000000D2 22222222 1 ??0028 DWORD 22222222h000000D6 33333333 1 ??0029 DWORD 33333333h000000DA 22222222 1 ??002A DWORD 22222222h000000DE 33333333 1 ??002B DWORD 33333333h000000E2 22222222 1 ??002C DWORD 22222222h000000E6 33333333 1 ??002D DWORD 33333333h000000EA 22222222 1 ??002E DWORD 22222222h000000EE 33333333 1 ??002F DWORD 33333333h000000F2 22222222 1 ??0030 DWORD 22222222h000000F6 33333333 1 ??0031 DWORD 33333333h000000FA 22222222 1 ??0032 DWORD 22222222h000000FE 33333333 1 ??0033 DWORD 33333333h00000102 22222222 1 ??0034 DWORD 22222222h00000106 33333333 1 ??0035 DWORD 33333333h0000010A 22222222 1 ??0036 DWORD 22222222h0000010E 33333333 1 ??0037 DWORD 33333333h00000112 22222222 1 ??0038 DWORD 22222222h00000116 33333333 1 ??0039 DWORD 33333333h0000011A 22222222 1 ??003A DWORD 22222222h0000011E 33333333 1 ??003B DWORD 33333333h00000122 22222222 1 ??003C DWORD 22222222h00000126 33333333 1 ??003D DWORD 33333333h0000012A 22222222 1 ??003E DWORD 22222222h0000012E 33333333 1 ??003F DWORD 33333333h00000132 22222222 1 ??0040 DWORD 22222222h00000136 33333333 1 ??0041 DWORD 33333333h0000013A 22222222 1 ??0042 DWORD 22222222h0000013E 33333333 1 ??0043 DWORD 33333333h00000142 22222222 1 ??0044 DWORD 22222222h00000146 33333333 1 ??0045 DWORD 33333333h0000014A 22222222 1 ??0046 DWORD 22222222h0000014E 33333333 1 ??0047 DWORD 33333333h00000152 22222222 1 ??0048 DWORD 22222222h00000156 33333333 1 ??0049 DWORD 33333333h0000015A 22222222 1 ??004A DWORD 22222222h0000015E 33333333 1 ??004B DWORD 33333333h00000162 22222222 1 ??004C DWORD 22222222h00000166 33333333 1 ??004D DWORD 33333333h0000016A 22222222 1 ??004E DWORD 22222222h0000016E 33333333 1 ??004F DWORD 33333333h00000172 22222222 1 ??0050 DWORD 22222222h00000176 33333333 1 ??0051 DWORD 33333333h0000017A 22222222 1 ??0052 DWORD 22222222h0000017E 33333333 1 ??0053 DWORD 33333333h00000182 22222222 1 ??0054 DWORD 22222222h00000186 33333333 1 ??0055 DWORD 33333333h0000018A 22222222 1 ??0056 DWORD 22222222h0000018E 33333333 1 ??0057 DWORD 33333333h00000192 22222222 1 ??0058 DWORD 22222222h00000196 33333333 1 ??0059 DWORD 33333333h0000019A 22222222 1 ??005A DWORD 22222222h0000019E 33333333 1 ??005B DWORD 33333333h000001A2 22222222 1 ??005C DWORD 22222222h000001A6 33333333 1 ??005D DWORD 33333333h000001AA 22222222 1 ??005E DWORD 22222222h000001AE 33333333 1 ??005F DWORD 33333333h000001B2 22222222 1 ??0060 DWORD 22222222h000001B6 33333333 1 ??0061 DWORD 33333333h000001BA 22222222 1 ??0062 DWORD 22222222h000001BE 33333333 1 ??0063 DWORD 33333333h000001C2 22222222 1 ??0064 DWORD 22222222h000001C6 33333333 1 ??0065 DWORD 33333333h000001CA 22222222 1 ??0066 DWORD 22222222h000001CE 33333333 1 ??0067 DWORD 33333333hWeatherReadings ENDS
运行调试:
10.4.3 FOR 伪指令
FOR伪指令通过迭代用逗号分隔的符号列表来重复一个语句块。列表中的每个符号都会引发循环的一次迭代过程。其语法如下:
FOR parameter,<argl,arg2arg3....>statements
ENDM
第一次循环迭代时,parameter取argl的值,第二次循环迭代时,parameter 取arg2 的值;以此类推,直到列表的最后一个实参。
学生注册示例 现在创建一个学生注册的场景,其中,COURSE 结构含有课程编号和学分值;SEMESTER结构包含一个有6门课程的数组和一个计数器 NumCourses
COURSE STRUCTNumber BYTE 9 DUP(?)Credits BYTE ?
COURSE ENDS
;semester含有一个课程数组
SEMESTER STRUCTCourses COURSE 6 DUP(<>)NumCourses WORD ?
SEMESTER ENDS
使用 FOR 循环可以定义 4个 SEMESTER 对象,每一个对象都从由尖括号括起的符号列表中选择一个不同的名称:
.data
FOR semName, <Fall2013, Spring2014, Summer2014, Fall2014>semName SEMESTER <>
ENDM
如果查看列表文件就会发现如下变量:
.data
Fall2013 SEMESTER <>
Spring2014 SEMESTER <>
Summer2014 SEMESTER <>
Fall2014 SEMESTER <>
.lst文件的结构:
完整代码测试笔记
;10.4.3_for.asm 10.4.3 FOR 伪指令
;FOR伪指令通过迭代用逗号分隔的符号列表来重复一个语句块。
;列表中的每个符号都会引发循环的一次迭代过程;.386
;.model flat, stdcall
;.stack 4096
;ExitProcess PROTO,dwExitCode:DWORDINCLUDE Irvine32.incCOURSE STRUCT Number BYTE 9 DUP(?) ;9个字节 课程编号Credits BYTE 9 ;1个字节 学分值
COURSE ENDS
;semester含有一个课程数组
SEMESTER STRUCTCourses COURSE 6 DUP(<>) ;60个字节NumCourses WORD 6 ;2个字节 课程门数
SEMESTER ENDS.data
array DWORD 11111111h, 22222222h, 33333333h, 44444444h
;定义一个标签以便访问数据
SemesterArray LABEL DWORD ;学期数组
FOR semName, <Fall2013, Spring2014, Summer2014, Fall2014>semName SEMESTER <>
ENDM
;扩展成4个结构
;Fall2013 SEMESTER <>
;Spring2014 SEMESTER <>
;Summer2014 SEMESTER <>
;Fall2014 SEMESTER <>
;获取数组长度:
SemesterArrayCount = ($ - SemesterArray) / TYPE SEMESTER.code
main PROCmov esi, OFFSET arraymov ecx, SemesterArrayCountmov esi, OFFSET Fall2013mov (SEMESTER PTR[esi]).NumCourses, 6mov ecx, 6 ;6门课程mov al, 65
L1:mov (COURSE PTR [esi]).Number, almov (COURSE PTR [esi]).Credits,clinc aladd esi, 10 ;一门课程占10个字节loop L1INVOKE ExitProcess, 0
main ENDP
END main
运行调试:
10.4.4 FORC 伪指令
FORC伪指令通过迭代字符串来重复一个语句块。字符串中的每个字符都会引发循环的一次迭代过程。其语法如下:
FORC parameter, <string>statements
ENDM
第一次循环迭代时,parameter等于字符串的第一个字符,第二次循环迭代时,parameter 等于字符串的第二个字符;以此类推,直到最后一个字符。下面的例子创建了一个字符查找表,其中包含了一些非字母字符。注意,<和>的前面必须有文字字符(!)运算符,以防它们违反FORC 伪指令的语法:
Delimiters LABEL BYTE
FORC code, <@#$%^&*!<!>>BYTE " &code"
ENDM
生成的数据表如下所示,可以在列表文件中查看:
00000000 40 1 BYTE "@"
00000001 23 1 BYTE "#"
00000002 24 1 BYTE "$"
00000003 25 1 BYTE "%"
00000004 5E 1 BYTE "^"
00000005 26 1 BYTE "&"
00000006 2A 1 BYTE "*"
00000007 3C 1 BYTE "<"
00000008 3E 1 BYTE ">"
完整代码测试笔记
;10.4.4_for.asm 10.4.4 FORC 伪指令
;FORC伪指令通过迭代字符串来重复一个语句块。
;字符串中的每个字符都会引发循环的一次迭代过程。;.386
;.model flat, stdcall
;.stack 4096
;ExitProcess PROTO,dwExitCode:DWORDINCLUDE Irvine32.inc.data
array DWORD 11111111h, 22222222h, 33333333h, 44444444h
Delimiters LABEL BYTE
FORC code, <@#$%^&*!<!>>BYTE "&code"
ENDM
;获取数组长度:
DelimitersCount = ($ - Delimiters) / TYPE BYTE.code
main PROCmov esi, OFFSET arraymov ecx, DelimitersCountmov esi, OFFSET DelimitersINVOKE ExitProcess, 0
main ENDP
END main
生成.lst文件内容:
00000000 .data00000000 11111111 array DWORD 11111111h, 22222222h, 33333333h, 44444444h22222222333333334444444400000010 Delimiters LABEL BYTEFORC code, <@#$%^&*!<!>>BYTE "&code"ENDM00000010 40 1 BYTE "@"00000011 23 1 BYTE "#"00000012 24 1 BYTE "$"00000013 25 1 BYTE "%"00000014 5E 1 BYTE "^"00000015 26 1 BYTE "&"00000016 2A 1 BYTE "*"00000017 3C 1 BYTE "<"00000018 3E 1 BYTE ">"00000019 ;获取数组长度:= 00000009 DelimitersCount = ($ - Delimiters) / TYPE BYTE00000000 .code 00000000 main PROC00000000 BE 00000000 R mov esi, OFFSET array00000005 B9 00000009 mov ecx, DelimitersCount0000000A BE 00000010 R mov esi, OFFSET DelimitersINVOKE ExitProcess, 00000000F 6A 00 * push +000000000h00000011 E8 00000000 E * call ExitProcess00000016 main ENDP END main
运行调试:
10.4.5 示例:链表
结合结构声明与REPEAT伪指令以指示汇编器创建一个链表的数据结构是相当简单的。链表中的每个节点都含有一个数据域和一个链接域:
在数据域中,一个或多个变量可以保存每个节点所特有的数据。在链接域中,一个指针包含了链表下一个节点的地址。最后一个节点的链接域通常是一个空指针。现在编写程序创建并显示一个简单链表。首先,程序定义一个节点,其中含有一个整数(数据)和一个指向下一个节点的指针:
ListNode STRUCTNodeData DWORD ? ;节点的数据NextPtr DWORD ? ;指向下一个节点的指针
ListNode ENDS
接着REPEAT 伪指令创建了ListNode 对象的多个实例。为了便于测试,NodeData 域含有一个整数常量,其范围为1~15。在循环内部,计数器加1并将值插入到ListNode域:
TotalNodeCount = 15
NULL = 0
Counter = 0
.data
LinkedList LABEL PTR ListNode
REPEAT TotalNodeCountCounter = Counter + 1ListNode <Counter, ($ + Counter * SIZEOF ListNode)>
ENDM
表达式($+Counter * SIZEOF ListNode)告诉汇编器把计数值与ListNode的大小相乘,并将乘积与当前地址计数器相加。结果值插入结构内的 NextPtr 域。[注意一个有趣的现象:位置计数器的值(S)固定在表的第一节点上。] 该表用尾节点(tail node)来标记末尾,其NextPtr 域为空(0 ):
ListNode <0,0>
当程序遍历该表时,它用下面的语句检索 NextPtr 域,并将其与 NULL 比较,以检查是否为表的末尾:
mov eax, (ListNode PTR[esi]).NextPtr
cmp eax, NULL
程序清单 完整的程序清单如下所示。在 main 中,一个循环遍历链表并显示全部的节点值。与使用固定计数值控制循环相比,程序检查是否为尾节点的空指针,若是则停止循环:
;10.4.5_List.asm 10.4.5 示例:链表
;结合结构声明与REPEAT伪指令以指示汇编器创建一个链表的数据结构是相当简单的。
;链表中的每个节点都含有一个数据域和一个链接域:;.386
;.model flat, stdcall
;.stack 4096
;ExitProcess PROTO,dwExitCode:DWORDINCLUDE Irvine32.inc
ListNode STRUCTNodeData DWORD ?NextPtr DWORD ?
ListNode ENDSTotalNodeCount = 15
NULL = 0
Counter = 0.data
LinkedList LABEL PTR ListNode
REPEAT TotalNodeCountCounter = Counter + 1ListNode <Counter, ($ + Counter * SIZEOF ListNode)>
ENDM
ListNode <0,0> ;尾节点.code
main PROCmov esi, OFFSET LinkedList
NextNode: mov eax, (ListNode PTR [esi]).NextPtr ;显示 NodeData域的值。cmp eax, NULL ;检查是否为尾节点。je quit;显示节点数据。mov eax, (ListNode PTR [esi]).NodeDatacall WriteDeccall Crlf;获得下一个节点的指针。mov esi, (ListNode PTR [esi]).NextPtrjmp NextNode
quit:INVOKE ExitProcess, 0
main ENDP
END main
运行调试:
10.4.6 本节回顾
1.简要说明WHILE 伪指令。
答:WHILE伪指令根据布尔表达式来重复语句块。
2.简要说明 REPEAT 伪指令。
答:REPEAT伪指令根据计算值来重复语句块。
3.简要说明 FOR 伪指令。
答:FOR伪指令通过遍历符号列表来重复语句块
4.简要说明 FORC 伪指令。
答:FORC伪指令通过遍历字符串来重复语句块。
5.哪条伪指令最适于生成字符查找表?
答:FORC
6.写出由下述宏生成的语句:
FOR val, <100,20,30>
BYTE 0, 0, 0, val
ENDM
答:
BYTE 0,0,0,100
BYTE 0,0,0,20
BYTE 0,0,0,30
7.设已定义如下宏mRepeat:
mRepeat MACRO char, countLOCAL L1mov cx, count
L1: mov ah, 2mov dl, charint 21hloop L1
ENDM
当按照下列语句(a,b 和 c)进行mRepeat 宏展开时,请写出预处理程序生成的代码;
mRepeat 'X', 50 ;a
mRepeat AL, 20 ;b
mRepeat byteVal, countVal ;c
8.挑战:在链表示例程序(10.4.5 节)中,如果REPEAT 循环的代码如下,那么程序运行结果如何?
mRepeat 'X', 50 ;a
1 mov cx, 50
1 ??0000: mov ah, 2
1 mov dl, 'X'
1 int 21h
1 loop ??0000
mRepeat AL, 20 ;b
1 mov cx, 20
1 ??0001: mov ah, 2
1 mov dl, AL
1 int 21h
1 loop ??0001
mRepeat byteVal, countVal ;c
1 mov cx, countVal1 ??0002: mov ah, 2
1 mov dl, byteVal
1 int 21h
1 loop ??0002
10.5 本章小结
结构(structure)是创建用户定义类型时使用的模板或模式。Windows API库中已经定义了大量的结构,用于实现应用程序与链接库之间的数据传递。结构可以包含不同类型的字段。使用字段初始值就可以对每个字段进行声明,即给该字段分配一个默认值。
结构自身不占内存空间,但是结构变量会占用内存。SIZEOF 运算符返回变量所占的字节数。
通过使用结构变量或形如[esi]的间接操作数,点运算符(.)对结构字段进行引用。如果是间接操作数来引用结构字段,那么必须使用PTR运算符指定结构类型,比如(COORDPTR [esi]).X。
结构包含的字段也可以是结构。醉汉行走程序(10.1.6节)给出了例子,其中的DrunkardWalk结构就包含了一个COORD 结构的数组。
宏通常在程序开始的时候定义,位于数据段和代码段之前。之后,在调用宏时,预处理程序就把宏代码复制插入到程序内调用发生的位置。
宏可以有效地作为过程调用的封装器(wrappers),以简化参数传递和寄存器人栈。比如宏mGotoxymDumpMem和mWriteString就是封装器的例子,它们调用本书链接库的过程。
宏过程(或宏)是指被命名的汇编语言语句块。宏函数与之类似,只不过宏函数会返回一个常量值。
条件汇编伪指令,如 IF、IFNB 和IFIDNI 可以被用于检测实参是否超出范围,是否缺失,以及是否为错误类型。ECHO 伪指令显示汇编时的错误消息,以提醒程序员将实参传递给宏时出现的错误。
替换运算符(&)解析对参数名的有歧义的引用。展开运算符(%)展开文本宏并将常量表达式转换为文本。文字文本运算符(<)把不同的字符和文本组合为一个文本。文字字符运算符(!)强制预处理程序将预定义运算符当作普通字符。
重复块伪指令能够减少程序的重复代码量。这些伪指令有:
●WHILE 伪指令根据一个布尔表达式来重复语句块。
●REPEAT伪指令根据计数器的值来重复语句块。
●FOR 伪指令通过遍历符号列表来重复语句块。
●FORC 伪指令通过遍历字符串来重复语句块。