让我们看看使用 32 位 Visual Studio 项目的 Debug 构建生成的程序集(默认设置):
这是我的程序:
extern "C" int func1(int x);
extern "C" int __stdcall func2(int x);
extern "C" int __cdecl func3(int x);
int main()
{
int x = 0;
func1(1);
func2(2);
func3(2);
return 0;
}
其中func1、func2 和func3 在单独的源文件中定义,以限制自动内联的可能性。
让我们看看生成的 main 汇编代码:
func1(1);
002117E8 push 1
002117EA call _func1 (0211159h)
002117EF add esp,4
func2(2);
002117F2 push 2
002117F4 call _func2@4 (0211131h)
func3(3);
002117F9 push 3
002117FB call _func3 (021107Dh)
00211800 add esp,4
对于 func1 和 func3,它是相同的签名。参数被压入堆栈,调用函数调用,然后堆栈寄存器(esp)被调整回(弹出)到它的先前地址 - 正如_cdecl 调用约定所期望的那样。在 __cdecl 调用约定中,调用者负责在函数调用完成后将堆栈指针恢复到其原始地址。
func2调用后,没有堆栈指针调整。与声明的 __stdcall 调用约定一致。在 __stdcall 调用中,编译后的函数负责弹回堆栈指针。检查 func1 与 func2 的程序集显示 func1 以:
00211881 ret // return, no stack adjustment
而 func2 以这个程序集结束:
002118E1 ret 4 // return and pop 4 bytes from stack
现在,在您断定“无链接属性”意味着“__cdecl”之前,请记住 Visual Studio 项目具有以下设置:
让我们将调用约定设置更改为 __stdcall,看看生成的程序集是什么样子的:
func1(1);
003417E8 push 1
003417EA call _func1@4 (034120Dh)
func2(2);
003417EF push 2
003417F1 call _func2@4 (0341131h)
func3(3);
003417F6 push 3
003417F8 call _func3 (034107Dh)
003417FD add esp,4
在调用 func1 后 main 突然没有弹出参数 - 因此func1 假定项目设置的默认调用约定。从技术上讲,这就是您的答案。
在某些环境中,__stdcall 作为默认值是常态。例如驱动程序开发...