Cra*_*Man 2 macros assembly preprocessor nasm
在我看来,%define定义单行宏的指令只是%assign具有附加功能(例如获取参数的能力)的指令。如果是这样的话,使用还有什么意义呢%assign?另外,关于%xdefine和equ呢?
这里的答案对我来说并不清楚,因为它太短了。我也阅读了文档,但我没有看到使用%assign.
摘要: %define只是文本替换。%assign是一个数值,因此您可以使用它来增加 a 内的计数器,%rep例如%assign i i + 1,这不适用于%define.
NASM 预处理器实际上提供了三个不同的指令来定义单行宏:%define、%xdefine和%assign。此外,还有第四种方法,即equ指令,它在汇编时评估等式,而不是作为预处理的一部分。
(稍后再说:NASM 预处理器可以与汇编器结合使用。在这种情况下,汇编器可以将标量数值传递给预处理器,并且这两个阶段不能严格分离为不同的通道。)
%define是纯文本替换。%assign与单行宏不同%define,它可以接受在宏名称后面的圆括号中指定的参数。由于%define接受文本,因此您还可以为其提供字符串数据,可以是带引号的字符串或纯文本(不带引号)。
如果您%define i i + 1然后评估生成的单行宏,它将评估为 text i + 1。预处理器显然足够复杂,不会将其变成无限循环。但是,它不会考虑之前为单行宏定义的内容i。如果您不提供命名i给汇编器的符号,它会抱怨该符号未定义。例子:
$ cat test1.asm
%define i 0
%define i i + 1
db i
$ nasm test1.asm
test1.asm:3: error: symbol `i' not defined
$ nasm test1.asm -E
%line 3+1 test1.asm
db i + 1
$
Run Code Online (Sandbox Code Playgroud)
%define另外,请注意,如果您使用将单行宏扩展为数值表达式,则运算符优先级可能会导致令人惊讶的结果。例子:
$ cat test2.asm
%define macro 1 + 3
db macro * 10h
$ nasm test2.asm -l /dev/stderr
1 %define macro 1 + 3
2 00000000 31 db macro * 10h
$
Run Code Online (Sandbox Code Playgroud)
请注意,计算值等于1 + (3 * 10h),而不是(1 + 3) * 10h。要获得后一个结果,%define您需要在内容中包含括号,例如%define macro (1 + 3),或在宏的使用周围包含括号,例如db (macro) * 10h。
%define对于用作文本替换而不是可以用数字处理的内容的实际示例,请考虑以下指令:
%define OT(num) (0 %+ num %+ h + OPTYPES_BASE)
Run Code Online (Sandbox Code Playgroud)
该num参数前面有一个数字零,后面h附加一个字母。因此,像 in 这样的OT(5B)文本被转换为格式良好的十六进制数。
%xdefine是一种为扩展传递给指令的任何文本后获得的文本定义单行宏的方法。
您可以使用%xdefine i i + 1来有效地增加单行宏的数值i,但这会很快耗尽处理宏所需的内存空间和处理时间。例子:
$ cat test3.asm
%define i 0
%rep 4
%xdefine i i + 1
%endrep
db i
$ nasm test3.asm -E
%line 5+1 test3.asm
db 0 + 1 + 1 + 1 + 1
$
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,%xdefine将文本附加 + 1到宏内容中而不是让预处理器实际计数的结果。因此,由 定义的宏%xdefine也可能导致与 相同的令人惊讶的运算符优先级%define。您可以再次使用括号,但如果您反复%xdefine在其内容中增长表达式并包含括号,那么所有括号也会累积。
%xdefine macro content一般相当于%define macro %[content]. 强制立即扩展宏的方括号结构是 NASM 预处理器的最新添加内容,这就是它%xdefine成为自己的指令而不是仅仅让用户使用方括号的原因。
重复的使用%xdefine主要是当您实际想要构建值列表(例如字符串或数字表达式或符号)时。例如,以下是构建符号列表的应用程序源代码的一部分:
%macro opsizeditem 3.nolist
%1 equ nextindex
%xdefine BITTAB_OPSIZEDITEMS BITTAB_OPSIZEDITEMS,%2
...
%endmacro
%assign nextindex 0
%define BITTAB_OPSIZEDITEMS ""
...
opsizeditem OP_IMM, ARG_IMMED, imm ; immediate
opsizeditem OP_RM,ARG_DEREF+ARG_JUSTREG,rm ; reg/mem
...
Run Code Online (Sandbox Code Playgroud)
首先将单行宏定义BITTAB_OPSIZEDITEMS为(空的带引号的字符串),然后每次使用该宏时""将多行宏的%2参数附加到列表中。这个列表的使用opsizeditem很简单:
bittab:
db BITTAB_OPSIZEDITEMS
Run Code Online (Sandbox Code Playgroud)
指令db通过单行宏传递,该宏将扩展为所需的列表。第一个条目将是带引号的空字符串。与单独的db ""指令一样,第一个条目扩展为根本没有程序集输出数据。所有后续条目均组装成一个字节的输出。(如果条目嵌入了逗号或包含带引号的字符串,则可以将其组装成多个字节。)
与之前的指令不同,%assign实际上将其内容计算为标量数值。使用它可以使预处理器发挥作用,而不仅仅是让它进行文本替换。例子:
$ cat test4.asm
%assign i 0
%rep 4
%assign i i + 1
%endrep
db i
$ nasm test4.asm -E
%line 5+1 test4.asm
db 4
$
Run Code Online (Sandbox Code Playgroud)
%assign实际上是在作业时评估其内容。接下来,它检查结果是否是标量数值;也就是说,不是需要任何重定位的符号表达式。然后将其格式化为带符号的 64 位十进制数。
副作用是,使用 定义的单行宏的扩展%assign始终被视为表达式中的单个项,因为它被扩展为单个项(可能包括减号)。重新审视我们之前的示例,%define但现在使用%assign:
$ cat test5.asm
%assign macro 1 + 3
db macro * 10h
$ nasm test5.asm -l /dev/stderr
1 %assign macro 1 + 3
2 00000000 40 db macro * 10h
$
Run Code Online (Sandbox Code Playgroud)
由于使用起来很直观macro,表达式扩展为等于 的值(1 + 3) * 10h。
以下是对表达式求值一次然后使用它两次的应用示例:
%assign %$index %$label + 1 - (2 * %$i)
%if %$index < 0
%error Invalid opindex content = %$index
%endif
[list +]
db %$index
[list -]
Run Code Online (Sandbox Code Playgroud)
如果我们按原样将其替换为表达式,则必须将表达式编写两次,并且预处理器和汇编器必须对其求值两次。如果我们使用的%define话,它仍然需要评估两次。
对于使用从汇编器获取数值的指令的示例%assign(如旁边所述),请考虑应用程序的这一部分:
%macro mne 1-2+;.nolist
%push
usesection ASMTABLE2, 1
%assign %$currofs $ - asmtab
%ifnempty %2
db %2
%endif
__SECT__
...
dw (%$currofs)<<4|%$string_size ; 12 bits for asmtab ofs, 4 for length
...
%pop
%define MNCURRENT %1%[MNSUFFIX]
%endmacro
Run Code Online (Sandbox Code Playgroud)
这种使用%assign避免了发明符号名称并将该符号永久输入到汇编器的符号表中。$相反,它计算标量值,即(当前节中的当前程序集位置)和符号之间的增量asmtab,并将结果分配给上下文本地单行宏%$currofs。然后使用该宏将数值写入不同的部分。(因此%define,或者仅仅使用使用的$ - asmtab文本%$currofs是不正确的,我们想要获取$该部分中的值ASMTABLE2。)然后,它会丢弃上下文,使用%pop它(理论上)允许预处理器丢弃所有上下文局部变量并回收他们使用的内存。
所有这三个预处理器指令也具有带有i前缀的相应形式,即%idefine、%ixdefine和%iassign。它们的行为与基本形式相同,只是单行宏的定义不区分大小写。
最后,还有equ。等式和定义之间的区别在于:
等式是永远的(它们只能被分配一个不能在整个程序集中更改的值),而定义则保留它们在源处理顺序中最后定义的扩展(特别是可以在程序集处理期间给予新的扩展),
Equate 可以计算为标量数值(也像%assignallowed 一样)或可重定位符号值(与 不同%assign),
等号不能计算为任意文本或带引号的字符串值(与 不同%define),
等式被输入到符号表中,以便它们显示在地图文件中,例如,
可以在定义之前引用等式(与任何单行宏不同),
最后,等式由汇编器阶段而不是预处理器阶段评估和处理。
标签可以被认为是等号的特殊情况,例如label:与 非常相似label equ $。(然而,这两种形式对局部标签机制的影响不同。真正的等式永远不会被视为局部标签的基本标签。真正的标签是。不过,两者都可以作为局部标签参与,因此.local:和.local equ $是完全相同的.) 标签和等式都可以用冒号或不带冒号来指定,但通常标签有冒号而等式没有冒号。最后,标签后面可以在同一行上跟指令,而等同则不能。