X86 STDCALL vs CDECL:`ret`vs`sub esp`与调用约定有关吗?
在中,第325页,在8.2.4 32位调用约定下 C调用约定。。。C调用约定以一种简单的方式解决了清理运行时堆栈的问题:当程序调用一个子例程时,它会在X86 STDCALL vs CDECL:`ret`vs`sub esp`与调用约定有关吗?,x86,x86-64,calling-convention,stdcall,cdecl,X86,X86 64,Calling Convention,Stdcall,Cdecl,在中,第325页,在8.2.4 32位调用约定下 C调用约定。。。C调用约定以一种简单的方式解决了清理运行时堆栈的问题:当程序调用一个子例程时,它会在调用指令之后使用一条语句向堆栈指针(ESP)添加一个等于子例程参数组合大小的值。下面是一个示例,其中在执行调用指令之前,将两个参数(5和6)推送到堆栈上 Example1 PROC push 6 push 5 call AddTwo add esp, 8 ret Example1 ENDP 因此,在子程序返回后,用C/C++编
调用
指令之后使用一条语句向堆栈指针(ESP
)添加一个等于子例程参数组合大小的值。下面是一个示例,其中在执行调用
指令之前,将两个参数(5和6)推送到堆栈上
Example1 PROC
push 6
push 5
call AddTwo
add esp, 8
ret
Example1 ENDP
因此,在子程序返回后,用C/C++编写的程序总是从调用程序的堆栈中删除参数
它接着说
STDCALL调用约定从堆栈中删除参数的另一种常见方法是使用名为STDCALL
的约定。在下面的AddTwo
过程中,我们向RET
指令提供了一个整数参数,返回调用过程后,该参数又向ESP添加8。整数必须等于过程参数消耗的堆栈空间字节数:
AddTwo PROC
push ebp
mov ebp,esp
mov eax,[ebp+12]
add eax,[ebp+8]
pop ebp
ret 8
AddTwo ENDP
应该指出的是,与C一样,STDCALL
,将参数按相反顺序推送到堆栈上。通过在RET
指令中使用prameter,STDCALL
减少了为子程序调用生成的代码量(一条指令),并确保调用程序永远不会忘记清理堆栈。另一方面,C调用约定允许子例程声明数量可变的参数。调用方可以决定要传递多少个参数
这段代码有点混乱,因为一个显示调用,另一个显示函数。为了简单起见,它们应该同时显示两者。为了调用约定,对堆栈的修改分为两个阶段
- 在为调用做准备时,将参数和推送到堆栈上
- 在被调用函数中,在堆栈上分配局部变量
RET
有关,而是在哪里进行清理。参数放置在调用之前的堆栈上,因此它们应该
RET
部分似乎让人分心,因为x86没有特定的调用约定。事实上,Windows 10在ARM上运行,而且在第一个示例中,编译器可以编写
ret 8
而不是
这也会产生同样的效果。事实上,它会保存一条指令
ret8
和addesp,8
ret
是不同的ret 8
首先从堆栈中弹出返回地址,然后通过向其添加立即值来调整ESP。此外,stdcall约定强制您在每次调用后“释放”本地堆栈空间,而使用C约定,您可以保持ESP
修改,并在原始函数返回后将该局部区域用于其他目的,例如为下一次调用准备参数或只是在那里存储一些局部区域,或者在使用单个子esp进行多次调用后释放所有参数,
,等等。。。和往常一样,在汇编中,你给指令组的上下文/解释/逻辑越多,你就越有可能错过一些微妙的细节或优化的可能性,你不会想得太多吧。A:显然是的?调用约定确实定义了子例程应该如何返回(即,使用什么机制给出子例程的返回点)以及CPU应该处于什么状态,因此子例程末尾的清理代码与调用约定有关(虽然您也可以用另一种方式实现它,但前提是您找到了更适合您的情况的方法,比如跳转到其他子例程的尾部而不是调用它,等等)=因此,我不确定问题是什么,答案中到底需要什么?问题是什么?STDCALL和CDECL在概念上的主要区别是CDECL允许var args,STDCALL按照调用方的预期处理参数,而CDECL按照调用方的使用来处理参数(是的,早期的C允许这样做)。
add esp,8
ret