我正在阅读汇编代码优化手册第 2.3 节常见编码陷阱 - 第 9 页
无与伦比的 PUSH 和 POP 指令。对于通过函数的所有可能路径,PUSH 和 POP 指令的数量必须相等。例子:
Run Code Online (Sandbox Code Playgroud)push ebx test ecx, ecx jz Finished ... pop ebx Finished: ; Wrong! Label should be before pop ebx ret
这里,如果 ECX 为零,则不会再次弹出推送的 EBX 值。结果是RET指令会弹出EBX之前的值并跳转到错误的地址。
我的疑问是:jz
指令不是将返回地址存储在堆栈中吗?什么其他指令一样jmp
,jg
,jge
,jl
,jle
等?
不,它没有。 调用指令将返回地址推入堆栈,跳转指令(包括条件跳转)不会。这就是调用和跳转之间的根本区别。
如果您想知道指令的作用,您应该始终参考官方文档中的说明,例如https://www.felixcloutier.com/x86/,这是英特尔手册的 HTML 副本。 (包括)的描述jcc
je
没有提到推送返回地址,它告诉你它没有这样做。另一方面,的描述call
明确指出返回地址被推送,并详细解释了它是如何完成的:
执行近调用时,处理器将 EIP 寄存器的值(包含 CALL 指令之后的指令的偏移量)压入堆栈(稍后用作返回指令指针)。处理器然后分支到目标操作数指定的当前代码段中的地址。
跳转指令通常用于在函数内转移控制,例如循环 ( while
)、条件执行 ( if
) 等结构。在大多数此类情况下,您永远不想返回跳转位置,因此推送返回地址会没用,只需要你浪费额外的指令来从堆栈中删除它。