MASM 中的宏是啥?
Posted
技术标签:
【中文标题】MASM 中的宏是啥?【英文标题】:What are macros in MASM?MASM 中的宏是什么? 【发布时间】:2019-01-06 04:54:01 【问题描述】:为什么在下面的代码中使用了宏?我不明白宏的概念
这是将十进制字符串转换为八进制字符串的代码(通过转换为整数):
prnstr macro msg
mov ah, 09h
lea dx, msg
int 21h
endm
data segment
buf1 db "Vuvedete desetichno chislo: $"
buf2 db 0ah, "Nevalidno chislo...$"
buf3 db 0ah, "Chisloto v osmichna broina sistema e : $"
buf4 db 6
db 0
db 6 dup(0)
multiplier db 0ah
data ends
code segment
assume cs:code, ds:data
start :
mov ax, data
mov ds, ax
mov es, ax
prnstr buf1
mov ah, 0ah
lea dx, buf4
int 21h
mov si, offset buf4 + 2
mov cl, byte ptr [si-1]
mov ch, 00h
subtract :
mov al, byte ptr [si]
cmp al, 30h
jnb cont1
prnstr buf2
jmp stop
cont1 :
cmp al, 3ah
jb cont2
prnstr buf2
jmp stop
cont2 :
sub al, 30h
mov byte ptr [si], al
inc si
loop subtract
mov si, offset buf4 + 2
mov cl, byte ptr [si-1]
mov ch, 00h
mov ax, 0000h
calc :
mul multiplier
mov bl, byte ptr [si]
mov bh, 00h
add ax, bx
inc si
loop calc
mov si, offset buf4 + 2
mov bx, ax
mov dx, 0000h
mov ax, 8000h
convert :
mov cx, 0000h
conv :
cmp bx, ax
jb cont3
sub bx, ax
inc cx
jmp conv
cont3 :
add cl, 30h
mov byte ptr [si], cl
inc si
mov cx, 0008h
div cx
cmp ax, 0000h
jnz convert
mov byte ptr [si], '$'
prnstr buf3
prnstr buf4+2
stop :
mov ax, 4c00h
int 21h
code ends
end star
【问题讨论】:
invoking macros in MASM 建议阅读 oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/toc.html 的第 8 章,了解 MASM 指令,如宏声明。不过,这不是一个重复的问题。 从整数转换为八进制的代码效率很低。八进制是 2 的幂,因此您可以简单地移位/与来获得 3 位组。div
是一种 非常 很慢的除以 8 的方法。缩进是一团糟,十进制->整数代码也是一团糟。实际的mul
循环并不可怕,但digit - '0'
和范围检查可以更有效地即时完成。相关:32 位 asm 中的 int->hex:How to convert a number to hex?。如果需要,很容易移植到 8086。
宏是宏,asm 或 C 它们的工作方式相同。您在哪里找到您替换的代码中使用的宏。 #define plus_one(a) (a+1) 然后当你在代码中看到它时 x = plus_one(3);替换为 x = (3+1);然后编译。与 asm 相同。节省了一遍又一遍地键入该代码。有点像 C 中的内联函数......
【参考方案1】:
来自 MASM 6.1 程序员指南:
宏是你给一系列字符(一个 文本宏)或一个或多个语句(宏过程或 函数)。
当你每次在代码中使用宏时,比如你在某行写prnstr buf2
,它会在汇编阶段被宏定义中的指令替换,也就是说,就像你在原始源代码中写的那样,有三行:
mov ah, 09h
lea dx, buf2 ; argument `msg` was replaced by `buf2` value
int 21h
因此,一个好的宏可以为您节省一些打字时间,并且可能有助于提高源代码的可读性。
现在我通常不赞成在汇编中使用宏,尤其是初学者,我将尝试在下面的文字中解释原因,但上面的部分是回答你的问题,如果它没有'不回答你的问题,请在 cmets 中告诉我。
宏会有点混淆将生成的机器代码,当您对某些源进行代码审查时,您会看到prnstr buf2
,您还必须打开宏定义以审查生成的代码是否仍然有意义,即,如果周围的代码不希望保留 dx
中的示例值,然后程序员添加了 prnstr
,忘记了它确实修改了 dx
值。所以审核起来比较麻烦。
这个主题也在调试器中继续,在调试器中你会看到反汇编的实际机器代码,所以你会看到所有那些真正的指令而不是源代码中使用的原始宏。这可能会使您更难以快速了解您在(源)代码中的位置,以及您正在调试的部分以及指令的来源。此外,如果特定的宏指令不适合并且您需要“修复”代码,则它可能比更改代码中的实际指令稍微困难一些(因为宏中的任何更改也会传播到它所在的所有其他地方使用)。
而且宏只保存源代码行,而不是最终的机器代码,所以如果在不完全理解效果的情况下过度使用它们,你可能会得到比预期大得多的二进制文件。
最后,宏是特定汇编程序的功能(除非汇编程序供应商有意尝试与其他供应商兼容,例如 TASM 能够编译几乎所有 MASM 源并在促销文本中用作“卖点”),而指令是由 CPU 定义的,因此如果您的目标平台(CPU + OS)有许多可用的竞争汇编器,没有宏的源代码可能在不同的汇编器之间更兼容(尽管通常也有少量的语法不兼容,使得“移植" 需要以任何方式手动编辑源代码的任务)。
在我看来,特定的prnstr
是合理的,尽管如果我要为初学者制作一些示例,我还是宁愿每次都用代码编写这些指令——它使用了 5 次,5+5 行 vs 5x3=15 行= 只缩短 5 行源代码,IMO 不值得,因为“代码编写”通常只占开发时间的 10-20%,而且节省的编写时间也不能节省代码阅读/审查和错误修复+维护(这两个是开发时间通常是写作的数倍)最终会使事情变得更糟。
初学者常见的宏(错误)使用模式是把它们误认为是程序。对于程序,x86 CPU 以指令的形式提供原生支持,例如 call
和 ret
,因此,如果代码段较长,您可以使用它们而不是宏,它们使代码读取更容易,因为每个 x86 asm 编码器应该知道它们,并且它们在调试器的反汇编中可见,就像编写源代码的方式一样。
现在,宏当然是 MASM 成为程序员出色工具的重要组成部分,因此它们当然也有好处:
性能与过程调用...call+ret
指令对需要花费一点时间来执行,而宏将指令直接注入原始流程。如果我们谈论的是每秒执行数百万次的密集内循环代码,那么宏而不是call
可能会产生一些可衡量的差异,有时会使结果更好(或更差)。但在其他情况下,这种担心对于现代 x86 来说是没有意义的,因为它们将在最短的时间内执行 call/ret
,而较短的二进制文件可能会带来不会像膨胀的扩展代码那样阻塞指令缓存的好处。
为不同类型的构建生成不同代码变体的好方法。您可以隐藏在宏中,例如 LOG(..)
代码的不同逻辑,在生产中为空,但在调试版本中生成调试日志,或者将过程调用的第三个参数放入 rcx
或 rdx
,具体取决于目标架构等...
以方便的方式使用新指令操作码(汇编器尚不支持)
可能还有更多,宏是强大的工具,可以将低级汇编变成中级编程语言。
再说一次,如果您现在需要汇编语言,您通常需要它来调整性能。这意味着重写用高级编程语言编写的少量其他代码来处理大量数据(通过分析原始代码发现的瓶颈),此时您通常不需要任何“中级”功能,因为您只需要精确地生成代码的内循环“热”部分,可能只有几十/几百行汇编代码,并将其连接到原始代码中,您可能不会发现任何大量使用宏这种情况。
我也可以想象在许多情况下宏会有所帮助(例如,如果您正在编写一些微基准测试,并且每个测试循环都有某些相同的部分,但您不希望它们成为“程序”的一部分,那么宏是完美的)等等......
但是,如果您只是学习汇编的基础知识,则根本不需要宏,而是花时间阅读有关计算机体系结构的更多理论并练习汇编本身。
【讨论】:
以上是关于MASM 中的宏是啥?的主要内容,如果未能解决你的问题,请参考以下文章