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

时间:2019-03-26 11:52:06

标签: assembly x86 global-variables local-variables i386

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

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

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

现在,我正在使用局部变量,并且参数要比全局变量更多。

谢谢。

1 个答案:

答案 0 :(得分:4)

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

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

在asm中,使用较少指令的代码通常更易于理解。通过删除存储/重新加载mov指令,而仅将数据保留在寄存器中通常会使操作更容易。用asm编写的乐趣在于找到用更少(和/或更便宜)的指令来完成相同工作的方法,而无用的存储/重装到内存中则相反。 IMO,这会使您的代码难看。


全局变量之所以会吸收asm,是因为它们会吸收高级语言的所有原因(函数读/写它们时数据流不清晰),以及您可能不会想到的高级语言的其他注意事项:每条使用像[my_var]这样的静态地址具有4字节的disp32作为寻址模式的一部分,而[esp+8]仅需要2个额外的字节(因为ESP是基数,所以是SIB;因为{ {1}}适合符号扩展的8位整数。或者,如果您使用EBP创建堆栈帧,则可以在寻址模式下保存SIB字节。

在玩具程序中,如果您不关心效率,而是使用标签和+8 / dd / {{1 }}而不只是偏移到堆栈框架中。但是在这种情况下,通常您只能将所有内容保存在寄存器中。 (特别是在x86-64上,除了堆栈指针之外,您还有15个GP寄存器,而IA-32只有7个,如果您将EBP用作帧指针则只有6个。)

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

也许也可以做一个简单的命名变量的方法,以举例说明,但是在asm中这就是注释的目的。没有编译器可以将您的自文档代码转换为高效代码。或者,您可以执行MSVC的asm输出,并为每个局部相对于堆栈框架的偏移量定义汇编时间常数。例如

dw

更好:将局部变量保存在寄存器中

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

如果不影响效率,通常在设计算法时,您可以在寄存器和要考虑的高级变量之间建立1:1对应关系。例如也许db对于整个函数都停留在foo equ -12 func: push ebp mov ebp, esp sub esp, 24 ... mov eax, [ebp + foo] leave ret 中,包括分支之后的所有块中。 (还有一些其他寄存器通常用作暂存空间,用于进行计算和从内存加载的内容。)

在这种情况下,您需要在记录此功能的函数顶部有一段注释。如果某些寄存器设置在函数顶部附近,则这些源代码行可能是此类注释的好地方。


内存目标x在典型的现代x86 ISA上具有6个周期的延迟(5个周期的存储转发+ 1个周期的ALU)。如果将其用作循环的一部分,它将以 best 的最佳状态运行,每6个循环重复一次。这就是为什么禁用优化的C编译器生成如此慢的代码的部分原因。手动执行此操作基本上是在脚上射击。

edi / sub dword [loop_counter], 1仅具有1个周期的延迟,因此,作为循环依赖项的一部分而没有任何存储/重载的循环,每个时钟周期可以运行1次迭代。 (对于当前Intel CPU上的最大循环为4 uops;如果底部宏的dec / jnz或dec ecx融合为一个uop,则最多为5条指令。否则,您将遇到前端瓶颈。 ,内存目标的读取-修改-写入操作始终至少为2 oups。)


何时使用全局变量

在BSS中分配大型阵列很容易进行测试。然后,您可以使用NASM语法中的jnz或MASM语法中的cmp/jcc将地址放入寄存器。因此,您可以使用它来测试编写的代码,该代码以指向数组的点作为输入。

静态常量数据很有用

最常见的用例可能是mov edi, array(或Windows上的mov edi, OFFSET array)中的字符串。

section .rodata

通常,您需要一个内存中的字符串来通过引用传递给系统调用,例如section .rdata或函数,例如section .rodata ; linked as part of the TEXT segment msg: db "Hello World", 10 msglen equ $ - msg ; assemble-time constant write(例如,格式字符串)。将其保存在只读存储器中并实现一个指针要比将字符串从立即数存储到存储器(例如使用puts

)要容易得多。
printf