第十章 结构和宏

10.1 结构

10.1.1 定义结构

name STRUCT
    field-declarations
name ENDS
Employee STRUCT
    IdNum BYTE "000000000"
    LastName BYTE 30 DUP(0)
    Years WORD 0
    SalaryHistory DWORD 0,0,0,0
Employee ENDS

使用ALIGN对齐

Employee STRUCT
    IdNum BYTE "000000000"              ; 9
    LastName BYTE 30 DUP(0)             ; 30
    ALIGN WORD                          ; 加 1 字节
    Years WORD 0                        ; 2
    ALIGN DWORD                         ; 加 2 字节
    SalaryHistory DWORD 0,0,0,0         ; 16
Employee ENDS                           ;共 60 字节

 

10.1.2 声明结构变量

.data
point1 COORD <5,10>             ; X = 5, Y = 10
point2 COORD <20>               ; X = 20, Y = ?
point3 COORD <>                 ; X = ?, Y = ?
worker Employee <>              ; 默认初始值

也可以使用 {} 初始化

 

TITLE testprogram

INCLUDE Irvine32.inc

COORD STRUCT
    X WORD ?
    Y WORD ?
COORD ENDS

NumPoints = 3
.data
XString BYTE "The X is:",0
YString BYTE "The Y is:",0
FLAG DWORD 1

ALIGN WORD
AllPoints COORD NumPoints DUP(<0,0>)

Show PROTO,Value:DWORD

.code
Display PROC
    push ebp
    mov ebp,esp
    push eax
    push edx
    .IF FLAG == 1
        mov edx,OFFSET XString
        neg FLAG
    .ELSE
        mov edx,OFFSET YString
        neg FLAG
    .ENDIF
    call WriteString
    mov eax,[ebp+8]
    call WriteDec
    call Crlf
    pop edx
    pop eax
    mov esp,ebp
    pop ebp
    ret 4
Display ENDP
main PROC
    mov edi,0
    mov ecx,NumPoints
    mov ax,1
L1:
    mov (COORD PTR AllPoints[edi]).X,ax
    movzx ebx, AllPoints[edi].X
    ;error:push AllPoints[edi].X
    push ebx
    call Display

    mov (COORD PTR AllPoints[edi]).Y,ax
    INVOKE Show, AllPoints[edi].Y
    call Crlf
    add edi,TYPE COORD
    inc ax
    loop L1
    call WaitMsg
    exit
main ENDP

Show PROC USES eax edx,
    Value:DWORD
    .IF FLAG == 1
        mov edx,OFFSET XString
        neg FLAG
    .ELSE
        mov edx,OFFSET YString
        neg FLAG
    .ENDIF
    call WriteString
    mov eax,Value
    call WriteDec
    call Crlf
    ret
Show ENDP
END main

 

; 结构    (ShowTime.asm)
INCLUDE Irvine32.inc
.data
sysTime SYSTEMTIME <>
XYPos COORD <3,1>
consoleHandle DWORD ?
colonStr BYTE ":",0

.code
main PROC
.WHILE 1
; 获取 Win32 控制台的标准输出句柄
    INVOKE GetStdHandle, STD_OUTPUT_HANDLE
    mov consoleHandle,eax

; 设置光标位置并获取系统时间
    INVOKE SetConsoleCursorPosition, consoleHandle, XYPos
    INVOKE GetLocalTime,ADDR sysTime
; 显示系统时间 (hh:mm:ss).
    movzx eax,sysTime.wHour          ; 小时
    call  WriteDec
    mov   edx,offset colonStr        ; ":"
    call  WriteString
    movzx eax,sysTime.wMinute        ; 分钟
    call  WriteDec
    call  WriteString                ; ":"
    movzx eax,sysTime.wSecond        ;
    call  WriteDec
    call Crlf
    mov eax,200
    call Delay
.ENDW
    exit
main ENDP
END main

 

10.1.7 联合的声明和使用

结构中的每个字段都有相对于结构第一个字节的偏移量,而联合 (union) 中所有的字段则都起始于同一个偏移量。一个联合的存储大小即为其最大字段的长度。如果不是结构的组成部分,那么需要用 UNION 和 ENDS 伪指令来定义联合:

unionname UNION
    union-fields
unionname ENDS

 

如果联合嵌套在结构内,其语法会有一点不同:

structname STRUCT
    structure-fields
    UNION unionname
        union-fields
    ENDS
structname ENDS

 

Integei; UNION
    D DWORD 0
    W WORD 0
    B BYTE 0
Integer ENDS

 

如果使用初始值,那么它们必须为相同的数值。

INCLUDE Irvine32.inc

Integer UNION
    D DWORD 1
    W WORD 5
    B BYTE 8
Integer ENDS

.data
Value Integer <>

.code
main PROC
    mov eax,Value.D
    call WriteDec
    call Crlf

    movzx eax,Value.W
    call WriteDec
    call Crlf

    movzx eax,Value.B
    call WriteDec
    call Crlf
    call WaitMsg
    exit
main ENDP
END main

 

10.2 宏

10.2.3 宏的调用

TITLE TESTPROGRAM

INCLUDE Irvine32.inc

mWrite MACRO text
    LOCAL string
    .data
    string BYTE text,13,10,0
    .code
    push edx
    mov edx,OFFSET string
    call WriteString
    pop edx
ENDM

.code
main PROC
    mWrite "Please Say Hello World!"
    mWrite "Please Say Hello World Again!"
    call WaitMsg
    exit
main ENDP
END main

 

10.2.4 宏的其他特性

1) 规定形参

利用 REQ 限定符,可以指定必需的宏形参。如果被调用的宏没有实参与规定形参相匹配,那么汇编器将显示出错消息。如果一个宏有多个规定形参,则每个形参都要使用 REQ 限定符。

下面是宏 mPutChar,形参 char 是必需的:

mPutchar MACRO char:REQ
push eax
mov al,char
call WriteChar
pop eax
ENDM

 

2) 宏注释

宏定义中的注释行一般都出现在每次宏展开的时候。如果希望忽略宏展开时的注释,就在它们的前面添加双分号 (;;)。示例如下:

mPutchar MACRO char:REQ
push eax ;; 提示:char 必须包含 8 个比特
mov al, char
call WriteChar
pop eax
ENDM

 

3) ECHO 伪指令

在程序汇编时,ECHO 伪指令写一个字符串到标准输出。下面的 mPutChar 在汇编时会显示消息“Expanding the mPutChar macro” :

mPutchar MACRO char:REQ
ECHO Expanding the mPutchar macro
push eax
mov al,char
call WriteChar
pop eax
ENDM

 

Visual Studio 2012 的控制台窗口不会捕捉 ECHO 伪指令的输出,除非在编写程序时将其设置为生成详细输出。设置方法如下:从 Tool 菜单选择 Options,选择 Projects and Solutions,选择 Build and Run,再从 MSBuild project build output verbosity 下拉列表中选择 Detailed。或者打开一个命令提示符并汇编程序。

首先,执行如下命令,调整 Visual Studio 当前版本的路径:   

"C:\Program Files\Microsoft Visual Studio 11.0\VC\bin\vcvars32"

 

然后,键入如下指令,其中 filename.asm 是程序的源代码文件名:

ml.exe /c /I "c:\Irvine" filename.asm

 

4) LOCAL 伪指令

宏定义中常常包含了标号,并会在其代码中对这些标号进行自引用。例如,下面的宏 makeString 声明了一个变量 string,且将其初始化为字符数组:

makestring MACRO text
.data
string BYTE text,0
ENDM

 

假设两次调用宏:

makeString "Hello"
makeString "Goodbye"

 

由于汇编器不允许两个标号有相同的名字,因此结果出现错误:

makeString "Hello"
1 .data
1 string BYTE "Hello",0
  makeString "Goodbye"
1 .data
1 string BYTE "Goodbye",0      ;错误!

 

使用 LOCAL

为了避免标号重命名带来的问题,可以对一个宏定义内的标号使用 LOCAL 伪指令。若标号被标记为 LOCAL,那么每次进行宏展开时,预处理程序就把标号名转换为唯一的标识符。下面是使用了 LOCAL 的宏 makeString:

makeString MACRO text
LOCAL string
.data
string BYTE text,0
ENDM

 

假设和前面一样,也是两次调用宏,预处理程序生成的代码会将每个string替换成唯一 的标识符:

makeString "Hello"
1 .data
1 ??0000 BYTE "Hello",0
  makeString "Goodbye"
1 .data
1 ??0001 BYTE "Goodbye",0

 

汇编器生成的标号名使用了  ??nnnn 的形式,其中 nnnn 是具有唯一性的整数。local 伪指令还可以用于宏内的代码标号。

 

5) 包含代码和数据的宏

宏通常既包含代码又包含数据。例如,下面的宏mWrite在控制台显示文本字符串:

mWrite MACRO text
LOCAL string ;;local号
.data ;;定义字符串
string BYTE text,0
.code
push edx
mov edx, OFFSET string
call WriteString
pop edx
ENDM

 

下面的语句两次调用宏,并向其传递不同的字符串文本:

mWrite "Please enter your first name"
mWrite "Please enter your last name"

 

汇编器对这两条语句进行展开时,每个字符串都被赋予了唯一的标号,且MOV指令也 作了相应的调整:

mWrite "Please enter your first name"
1 .data
1 ??0000 BYTE "Please enter your first name",0
1 .code
1 push edx
1 mov edx, OFFSET ??0000
1 call WriteString
1 pop edx
  mWrite "Please enter your last name"
1 .data
1 ??0001 BYTE "Please enter your last name", 0
1 .code
1 push edx
1 mov edx, OFFSET ??0001
1 call Writestring
1 pop edx

 

6) 宏嵌套

被其他宏调用的宏称为被嵌套的宏 (nested macro)。当汇编器的预处理程序遇到对被嵌套宏的调用时,它会就地展开该宏。传递给主调宏的形参也将直接传递给它的被嵌套宏。

提示:使用模块方法创建宏。保持它们的简短性,以便将它们组合到更复杂的宏内。这样有助于减少程序中的复制代码量。

【示例】下面的宏 mWritein 写一个字符串文本到控制台,并添加换行符。它调用宏 mWrite 和 Crlf 过程:

mWriteln MACRO text
mWrite text
call Crlf
ENDM

 

形参 text 被直接传递给 mWrite。假设用下述语句调用 mWriteln:

mWriteln "My Sample Macro Program"

在结果代码展开,语句旁边的嵌套级数(2)表示被调用的是一个嵌套宏:

mWriteln "My Sample Macro Program"
2   .data
2   ??0002 BYTE "My Sample Macro Program",0
2   .code
2   push edx
2   mov    edx,OFFSET ??0002
2   call WriteString
2   pop    edx
1   call Crlf

 

10.2.5 使用本书附带的宏库

INCLUDE Macros.inc

 

宏名形式参数说明
mDump varName, useLabel 用变量名和默认属性显示一个变量
mDumpMem abbress, itemCount, componentsize 显示内存区域
mGotoxy X,Y 将光标位置设置在控制台窗口缓冲区
mReadString varName 从键盘读取一个字符串
mShow itsName, format 用各种格式显示一个变量或寄存器
mShowRegister itsName, regValue 显示32位寄存器名,并用十六进制显示其内容
mWrite text  向控制台窗口输出一个字符串文本
mWriteSpace count 向控制台窗口输出一个或多个空格
mWriteString buffer 向控制台窗口输岀一个字符串变量的内容

 

10.3 条件汇编伪指令

 

 

伪指令说明
IF expression 若 expression 为真(非零)则允许汇编。可能的关系运算符为 LT、GT、EQ、NE、LE 和 GE
IFB<argument> 若 argument 为空则允许汇编。实参名必须用尖括号(<>)括起来
IFNB<argument> 若 argument 为非空则允许汇编。实参名必须用尖括号(<>)括起来
IFIDN<arg1>,<arg2> 若两个实参相等(相同)则允许汇编。采用区分大小写的比较
IFIDNI<arg1 >,<arg2> 若两个实参相等(相同)则允许汇编。采用不区分大小写的比较
IFDIF<arg1 >,<arg2> 若两个实参不相等则允许汇编。采用区分大小写的比较
IFDIFI<arg1>,<arg2> 若两个实参不相等则允许汇编。采用不区分大小写的比较
IFDIF name 若 name 已定义则允许汇编
IFNDEF name 若 name 还未定义则允许汇编
ENDIF 结束用一个条件汇编伪指令开始的代码块
ELSE 若条件为真,则终止汇编之前的语句。若条件为假,ELSE 汇编语句直到遇到下一个 ENDIF
ELSEIF expression 若之前条件伪指令指定的条件为假,而当前表达式为真,则汇编全部语句直到出现 ENDIF
EXITM  立即退出宏,阻止所有后续宏语句的展开

 10.3.2 默认参数的初始化

宏可以有默认参数初始值。如果调用宏出现了宏参数缺失,那么就可以使用默认参数。其语法如下:

paramname := < argument >

运算符前后的空格是可选的。比如,宏 mWriteln 提供含有一个空格的字符串作为其默认参数。如果对其进行无参数调用,它仍然会打印一个空格并换行:

mWriteln MACRO text:=<" ">
    mWrite text
    call Crlf
ENDM

若把空字符串 (" ") 作为默认参数,那么汇编器会产生错误,因此必须在引号之间至少插入一个空格。

 

10.3.3 布尔表达式

 

 

LT 小于
GT 大于
EQ 等于
NE 不等于
LE 小于等于
GE 大于等于

10.3.7 特殊操作符

& 替换运算符
<> 文字文本运算符
! 文字字符运算符
% 展开运算符

替换操作符(&)

TITLE TESTPROGRAM

INCLUDE Irvine32.inc

mShowRegister MACRO regName
.data
    tempStr BYTE "regName=",0
    tempStr1 BYTE "&regName=",0

.code
    mov edx,OFFSET tempStr
    call WriteString
    call Crlf
    mov edx,OFFSET tempStr1
    call WriteString
    call Crlf
ENDM

.code
main PROC
    mShowRegister "Hello LINUX!"
    call WaitMsg
    exit
main ENDP
END main

但是VS上会报错,只能tempStr BYTE &regName

 

展开运算符(%)

展开运算符(%)展开文本宏并将常量表达式转换为文本形式。有几种方法实现该功能。若使用的是 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

 

屏幕输出没什么用:

The array contains (SIZEOF array) bytes
The array contains %(SIZEOF array) bytes

 

反之,如果用 TEXTEQU 编写包含 (SIZEOF array) 的文本宏,那么该宏就可以展开为之后的代码行:

TempStr TEXTEQU %(SIZEOF array)
%   ECHO The array contains TempStr bytes

 

产生的输出如下所示:

The array contains 32 bytes

 

文字文本运算符(<>)

文字文本(literal-text)运算符(<>)把一个或多个字符和符号组合成一个文字文本,以防止预处理程序把列表中的成员解释为独立的参数。

在字符串含有特殊字符时该运算符非常有用,比如逗号、百分号(%)、和号(&)以及分号(;),这些符号既可以被解释为分隔符,又可以被解释为其他的运算符。例如,之前给岀的宏 mWrite 接收一个字符串文本作为其唯一的实参。如果传递的字符串如下所示,预处理程序就会将其解释为3个独立的实参:

mWrite "Line three", 0dh, 0ah

 

第一个逗号后面的文本会被丢弃,因为宏只需要一个实参。然而,如果用文字文本运算 符将字符串括起来,那么预处理程序就会把尖括号内所有的文本当作一个宏实参:

mWrite <"Line three", 0dh, 0ah>

 

文字字符运算符(!)

构造文字字符(literal-character)运算符(!)的目的与文字文本运算符的几乎完全一样:强制预处理程序把预先定义的运算符当作普通的字符。在下面的 TEXTEQU 定义中,运算符 ! 可以防止符号 > 被当作文本分隔符:

BadYValue TEXTEQU <Warning: Y-coordinate is !> 24>

 

警告信息示例

下面的例子有助于说明运算符 %、& 和 ! 是如何工作的。假设已经定义了符号 BadYValue。现在创建一个宏 ShowWarning,接收一个用引号括起来的文本实参,并将其传递给宏 mWrite。注意替换(&)运算符的用法:

ShowWarning MACRO message
    mWrite "&message"
ENDM

 

然后调用 ShowWarning,把表达式 %BadYValue 传递给它。% 运算符计算(解析) BadYValue,并生成与之等价的字符串:

.code
ShowWarning %BadYValue

 

正如所期望的,程序运行并显示警告信息:

Warning: Y-coordinate is > 24

 

10.4 定义重复块

MASM 有许多循环伪指令用于生成重复的语句块:WHILE、REPEAT、FOR 和 FORC。与 LOOP 指令不同,这些伪指令只在汇编时起作用,并使用常量值作为循环条件和计数器:

  • WHILE 伪指令根据一个布尔表达式来重复语句块。
  • REPEAT 伪指令根据计数器的值来重复语句块。
  • FOR 伪指令通过遍历符号列表来重复语句块。
  • FORC 伪指令通过遍历字符串来重复语句块。

 

posted @ 2019-11-12 17:05  Hk_Mayfly  阅读(378)  评论(0)    收藏  举报