在x86汇编中,何时应该使用全局变量而不是局部变量?

问题描述 投票:2回答:1

我正在用x86汇编创建一些小程序,这是我第一次使用低级语言,所以我不习惯它。

在高级语言中,我很少使用全局变量,但是我已经看到很多教程在汇编中使用全局变量,所以我不确定何时使用全局变量而不是局部变量。

全局变量是指在.bss和.data段中创建的数据,而局部变量是指使用堆栈指针在当前过程的堆栈上分配的数据。

现在,我使用的是局部变量,而且参数比全局变量多得多。

提前致谢。

assembly x86 global-variables local-variables i386
1个回答
4
投票

是的,更喜欢保存在寄存器中的本地人,或者在需要时在堆栈中。

“变量”是一个高级概念,并不是真正存在于asm中。所以这只是一个问题,即你在哪里保存你正在处理的数据。但是可以肯定的是,本地与全局变量是讨论静态存储(.data / .bss / .rodata)与堆栈内存的好方法,如果你考虑的是未经优化的C,其中每个变量确实都有内存中的地址。

在asm中,具有较少指令的代码通常更容易理解。删除存储/重新加载mov指令有利于仅将数据保存在寄存器中通常使得更容易遵循。在asm中写作的乐趣在于找到用更少(和/或更便宜)的指令来完成相同工作的方法,并且无用地存储/重新加载到存储器就是这样。这让你的代码变得丑陋,IMO。


Globals因为他们吸收更高级别语言的原因(当函数读/写它们时数据流不清楚)以及您在高级语言中可能没有想到的其他注意事项而感到厌烦:每个使用静态地址的指令都是如此[my_var]有一个4字节的disp32作为寻址模式的一部分,而[esp+8]只需要2个额外的字节(SIB因为ESP作为基础,而disp8因为+8适合符号扩展的8位整数)。或者,如果使用EBP创建堆栈帧,则将SIB字节保存在寻址模式中。

如果你不关心效率而宁愿用标签和dd / dw / db定义你的记忆布局而不是仅仅偏移到堆栈帧中,那么Globals在玩具程序中可能是合理的,基本上只是一个功能。但在这种情况下,通常你可以将所有内容保存在寄存器中。 (特别是在x86-64上,你有15个GP寄存器而不是堆栈指针,而IA-32只有7个,如果你把EBP专用于帧指针则为6个。)

在asm示例/教程中使用大量全局变量可能是旧的ISA(如6502或8051)中没有堆栈指针相对寻址模式的剩余样式习惯,因此调用堆栈上的局部变量是一件坏事。 (见Why do C to Z80 compilers produce poor code?

它也可以/作为一个简单的方法来命名变量,以便做一个例子,但是在asm这是注释的用途。没有编译器可以将您的自我记录代码转换为有效的代码。或者你可以做MSVC的asm输出,并为每个局部相对于堆栈帧的偏移定义汇编时常数。例如

foo equ -12
func:
   push  ebp
   mov   ebp, esp
   sub   esp, 24
   ...

   mov  eax, [ebp + foo]
   leave
   ret

更好的是:将本地变量保存在寄存器中

对于大多数变量,通常无需将它们溢出到任何地方的内存中。使用注释来跟踪哪个变量或表达式在哪里。

如果它不会影响效率,通常您可以在寄存器和设计算法时考虑的高级变量之间建立1:1的对应关系。例如也许x留在edi的整个功能,包括分支后的所有块。 (还有一些其他寄存器主要用作临时空间用于计算和从内存加载的东西。)

在这种情况下,您在记录此功能的函数顶部会有一个注释块。如果某些寄存器设置在函数顶部附近,那么这些源代码行可以成为这些注释的好地方。


内存目标sub dword [loop_counter], 1在典型的现代x86 ISA上具有6个周期延迟(5个周期存储转发+ 1个周期ALU)。如果将此作为循环的一部分使用,则每6个循环最多运行一次迭代。这是为什么禁用优化的C编译器会生成如此慢的代码的部分原因。用手做这件事基本上就是把自己甩在脚下。

dec ecx / jnz只有1个周期延迟,因此没有任何存储/重载作为循环携带依赖的一部分的循环可以在每个时钟周期运行1次迭代。 (对于当前Intel CPU上最多4个uop的循环;如果底部宏的dec / jnz或cmp/jcc融合到一个uop中,则最多5个指令。否则你会遇到前端瓶颈。说到内存目的地read-modify-write操作总是至少2 uops。)


When to use globals

在BSS中分配大型阵列很容易进行测试。然后,您可以使用NASM语法中的mov edi, array或MASM语法中的mov edi, OFFSET array将地址输入到寄存器中。因此,您可以使用它来测试编写的代码,以将数组作为输入。

Static constant data is useful

最常见的用例可能是section .rodata(或Windows上的section .rdata)中的字符串。

section .rodata                     ; linked as part of the TEXT segment
msg: db "Hello World", 10
msglen equ $ - msg                  ; assemble-time constant

通常你需要一个内存中的字符串来传递一个系统调用,如write或像putsprintf这样的函数(例如作为格式字符串)。将它放在只读存储器中并实现指针比从字符串存储字符串容易起来要容易得多,例如使用push `rld\n`

mov dword [esp], "Hell"
mov dword [esp+4], "o Wo"
...
mov ecx, esp              ; pointer to the string
© www.soinside.com 2019 - 2024. All rights reserved.