使用 %assign 而不是 %define 是否有意义?

Is there a point in using %assign over %define?

在我看来,定义单行宏的 %define 指令只是 %assign 指令具有附加功能,例如获取参数的能力。如果是这样,使用 %assign 有什么意义?另外,%xdefineequ 呢?

答案 我不太清楚,因为它太短了。我也阅读了文档,但我没有看到使用 %assign.

有任何优势

总结: %define只是文字替换。 %assign 是一个数值,因此您可以使用它来增加 %rep 中的计数器,例如 %assign i i + 1,这不适用于 %define.


实际上 NASM 预处理器提供了三种不同的指令来定义 single-line 宏:%define%xdefine%assign。此外,还有第四种方法,即 equ 指令,它在 assemble 时计算等式,而不是作为预处理的一部分。

(留待以后:NASM 预处理器可以与 assembler 结合使用。在这种情况下,assembler 可以将标量数值传递给预处理器,并且这两个阶段不能作为不同的阶段严格分开。)


%define 是 text-only 的替代品。与 %assign 不同,%define single-line 宏可以接受参数,在宏名称后面的圆括号中指定。因为 %define 接受文本,您还可以为其提供字符串数据,引号字符串或纯文本(不带引号)。

如果您 %define i i + 1 然后计算生成的 single-line 宏,它将计算为文本 i + 1。预处理器显然足够复杂,不会将其变成无限循环。但是,它不会考虑先前定义到 single-line 宏 i 的内容。如果您不向 assembler 提供名为 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
$

此外,请注意,如果您使用 %define 将 single-line 宏扩展为数值表达式,运算符优先级可能会导致令人惊讶的结果。示例:

$ 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
$

请注意,计算值等于 1 + (3 * 10h),而不是 (1 + 3) * 10h。要使用 %define 获得后一个结果,您需要在内容中包含括号,如 %define macro (1 + 3),或围绕宏的使用,如 db (macro) * 10h.

有关使用 %define 作为文本替换而不是可以用数字处理的内容的实际示例,请考虑 this directive:

%define OT(num) (0 %+ num %+ h + OPTYPES_BASE)

num 参数前面有一个零数字,并附加了一个 h 字母。因此,文本 like in OT(5B) 被转换为 well-formed 十六进制数。


%xdefine 是一种为扩展传递给指令的任何文本后获得的文本定义 single-line 宏的方法。

您可以使用 %xdefine i i + 1 有效地增加 single-line 宏 i 的数值,但是这会很快耗尽内存 space 和所需的处理时间处理宏。示例:

$ 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
$

如您所见,%xdefine 导致将文本 + 1 附加到宏内容,而不是使预处理器实际计数。因此,由 %xdefine 定义的宏也可能导致与 %define 相同的令人惊讶的运算符优先级。您可以再次使用括号,但如果您重复 %xdefine 以在其内容中增加表达式并包含括号,那么所有括号也会累积。

%xdefine macro content 通常等同于 %define macro %[content]。强制立即扩展宏的方括号构造是 NASM 预处理器的最新添加,这就是 %xdefine 成为其自己的指令而不是仅仅让用户使用方括号的原因。

重复 %xdefine 的使用主要用于当您实际想要构建值列表时,例如字符串或数字表达式或符号。例如,here is a part of an application's source 构建符号列表:

%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
...

这开始定义 single-line 宏 BITTAB_OPSIZEDITEMS"" (空引号字符串),然后将 multi-line 宏的 %2 参数附加到每次使用 opsizeditem 宏时的列表。 use of this list 很简单:

bittab:
        db BITTAB_OPSIZEDITEMS

A db 指令被传递给 single-line 宏,它扩展到所需的列表。第一个条目将是带引号的空字符串。与单独的 db "" 指令一样,第一个条目扩展为根本没有汇编输出数据。所有后续条目 assemble 每个输出一个字节。 (如果一个条目嵌入了逗号或由带引号的字符串组成,则它可以 assembled 成多个字节。)


与之前的指令不同,%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
$

%assign 实际上是在赋值点计算它的内容。接下来是 checks that the result is a scalar numeric value; that is, not a symbol expression requiring any relocation. It is then formatted as a signed 64-bit decimal number.

作为副作用,使用 %assign 定义的 single-line 宏的扩展总是被视为表达式中的单个项,因为它 扩展为单个术语(可能包括减号)。重温我们为 %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
$

这个 macro 的使用很直观,表达式扩展为等于 (1 + 3) * 10h 的值。

这里有一个应用示例of evaluating an expression once然后使用它两次

%assign %$index %$label + 1 - (2 * %$i)
%if %$index < 0
 %error Invalid opindex content = %$index
%endif
[list +]
    db %$index
[list -]

如果我们要用表达式替换它,我们将不得不编写两次表达式,预处理器和 assembler 将不得不对其求值两次。如果我们使用 %define 那么它仍然需要计算两次。

对于使用从 assembler 获取数值的 %assign 指令的示例(如旁文所述),请考虑 this part of the application:

    %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

%assign 的这种使用避免了发明符号名称并将该符号永久输入到 assembler 的符号 table 中。相反,它计算标量值,即 $(当前节中的当前程序集位置)和符号 asmtab 之间的增量,并将结果分配给 context-local single-line 宏%$currofs。然后使用此宏将数值写入不同的部分。 (因此 %define 或仅在使用 %$currofs 的地方使用 $ - asmtab 文本是不正确的,我们希望在 ASMTABLE2 部分中获得 $ 值。 ) 然后,它使用 %pop 丢弃上下文,这(理论上)允许预处理器丢弃所有 context-local 变量并回收它们使用的内存。


所有这三个预处理器指令也有相应的形式,带有 i 前缀,即 %idefine%ixdefine%iassign。这些行为与基本形式相同,除了 single-line 宏定义为 case-insensitively.


最后是equ。等式和定义的区别在于:

  1. 等式是永远的(它们只能被分配一个不能在整个程序集中改变的值),而定义保持它们的扩展,因为它们在源处理顺序中最后定义(特别是可以是在组装过程中给出新的扩展),

  2. 等式可以计算为标量数值(如 %assign 也允许)或可重定位的符号值(与 %assign 不同),

  3. 等式不能计算为任意文本或带引号的字符串值(与 %define 不同),

  4. 符号 table 中输入了等式,以便它们显示在地图文件中,例如,

  5. 等式可以在定义之前被引用(不同于任何 single-line 宏),

  6. 最后,等式由 assembler 阶段计算和处理,而不是预处理器阶段。

标签可以被认为是等式的特例,例如label:label equ $非常相似。 (然而,这两种形式对 local-label 机制的影响不同。真正的等式永远不会被视为本地标签的基本标签。真正的标签是。两者都可以参与 作为本地标签 ,因此 .local:.local equ $ 完全相同。)标签和等号都可以用冒号或不带冒号来指定,尽管通常标签有冒号而等号没有有它。最后,标签后可以跟在同一行的指令,而等号不能。