【发布时间】:2021-01-23 20:10:49
【问题描述】:
我正在学习一些有趣的汇编(目前在 Windows 上使用 NASM),我对stdcall calling convention 和具有可变数量参数的函数有疑问。例如,sum 函数接受 X 个整数并将它们加在一起。
由于调用者在使用stdcall时需要清理/重置堆栈,而ret只能使用常量值,所以我一直想知道popping返回地址有什么问题,移动esp,然后自己跳回调用者,而不是使用ret。我认为这会更慢,因为它需要更多指令,但可以接受吗?
; int sum(count, ...)
sum:
mov ecx, [esp+4] ; count
; calc args size
mov eax, ecx ; vars count
inc eax ; + count
mov edx, 4 ; * 4 byte per var
mul edx
mov edx, eax
xor eax, eax ; result
cmp ecx, 0 ; if count == 0
je .done
inc ecx ; count++, to start with last arg
.add:
add eax, [esp+4*ecx]
dec ecx ; if --ecx != 1, 0 = return, 1 = count
cmp ecx, 1
jnz .add
.done:
pop ebx
add esp,edx
jmp ebx
我不明白为什么这样会不行,而且它似乎可以工作,但我读过文章谈到 stdcall 无法处理变量参数,因为函数不知道什么传递给ret 的值。我错过了什么吗?
【问题讨论】:
-
即使在
stdcall下,变量参数也会切换到调用者清理。 -
我认为在 Windows 上,可变参数函数永远不是标准调用。如果大多数库函数是 stdcall 或 fastcall(32 位版本的 fastcall 的 callee-pops),像 printf 这样的函数是 cdecl (caller-pops)
-
我明白了。好吧,让我们不要挂断我称它为“stdcall”,而是关注我自己的调用约定是否有任何问题,我什至在带有可变参数的函数上清理堆栈^^
-
除了性能和需要临时寄存器之外,是的,您可以弹出一个寄存器,调整
(r/e)sp,然后跳转到该寄存器的内容。性能也可能会受到影响,因为您可能会混淆分支预测器的函数调用处理。该寄存器显然需要可用,因为您以后无法恢复它。注意add或sub指向堆栈指针会修改状态标志;使用lea或pop来避免这种情况。 -
@ecm:有趣的事实:如果您返回
push reg/ret而不是jmp ebx,分支预测会很好(因为您仍然返回同一个地方。返回地址预测有效通过假设 call/ret 嵌套正确(返回地址的内部堆栈式硬件数据结构),与 ESP 指向的位置无关)。
标签: assembly x86 variadic-functions calling-convention