windows calling conventions

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/include_heqile/article/details/100101524

参考自:http://unixwiz.net/techtips/win32-callconv.html

__cdecl

在C语言的调用过程中,调用一个函数就是往栈上push参数

push arg1
push arg2
push arg3
call function
add  sp,12		这个操作是清理栈空间,sp是栈顶指针

当调用结束的时候,调用者会负责清除栈空间

__stdcall

__cdecl一样,参数也是被调用者负责将参数压入栈中,与之不同的是,调用结束后,栈是由被调用者负责清理的

push arg1
push arg2
push arg3
call function
//add  sp,12		__stdcall没有这一步操作,因为清理栈空间的操作是由被调用者负责的

windows中的WINAPI就是__cdecl

windows.h:

#define WINAPI __stdcall

从上面我们可以看出来,如果调用者被调用者关于栈的清理工作有分期的话,那么后果将是灾难性的(没有人清理程序的栈空间

相比较__cdecl而言,__stdcall的好处就是你不用每次调用函数的时候都操心栈空间清理的事了,因为被调用者会自己清理栈,这个特性在一些比较小的项目中是看不出来很明显的效果的,但是随着软件体积的增大,这个区别会造成显著的影响

对于可变参的函数,实现__stdcall几乎是不可能的,因为它根本就不知道传了多少参数进来,因此栈空间的清理工作也就只能由调用者来完成了

也就是说__stdcall不支持可变长参数的函数的,只有__cdecl可以

其实两者谁好谁差并没有一个严格的说法,只是说不要将函数和calling convention混用即可,比如对printf使用__stdcall进行修饰,使用错误的calling convention去调用一个函数将会破坏栈空间

上面说的是C的calling convention,windows对此进行了一些改进

上面提到,使用错误的calling convention会对程序的运行造成极大的负面影响,为了避免发生这种情况,windows实现了一个机制

windows对这些calling convention进行了一些修饰(decoration),不过这个修饰是在底层做的,我们是无法直接在代码上观察到的

  • 对于使用__cdecl的调用,windows会将该函数编码成下面这个样子
    • 修饰前:void __cdecl foo(void);
    • 修饰后:_foo,仅仅是在函数名前面加了个下划线来进行标记
  • 对于使用__stdcall的调用,windows会将该函数编码成下面这个样子
    • 修饰前:void __stdcall foo(int a);
    • 修饰后:_foo@4
    • @后面的数字表示传进来的参数的字节数,这样即使是printf这种可变长参数函数也可以使用__stdcall,这样一来也就不可能发生convention的误用了,即使传入的参数和定义的参数数量不一致也没关系,因为@后面跟的是传入参数的字节数而不是参数个数

上面提到的修饰的工作,是由(链接器)linker完成的

我们可以自己写一段代码,只声明函数,但是不进行实现,然后进行编译,从报错信息中可以看到编码后的函数的样子

testfile.c:

extern void __stdcall func1(int a);
extern void __stdcall func2(int a, int b, double d);
extern void __cdecl   func3(int b);
extern void __cdecl   func4(int a, int b, double d);

int __cdecl main(int argc, char **argv)
{
        func1(1);

        func2(2, 3, 4.);

        func3(5);

        func4(6, 7, 8.0);

        return 0;
}

执行命令cl /nologo testfile.c,结果如下:

testfile.obj : error LNK2001: unresolved external symbol _func1@4  ... __stdcall
testfile.obj : error LNK2001: unresolved external symbol _func2@16 ... __stdcall
testfile.obj : error LNK2001: unresolved external symbol _func3    ... __cdecl
testfile.obj : error LNK2001: unresolved external symbol _func4    ... __cdecl
testfile.exe : fatal error LNK1120: 4 unresolved externals

刚才用windows 2012试了一下,发现错误输出和上面的并不完全一样

因为上面的是C语言,C的decoration规则和C++是不一样的

参考链接

对于类printf函数而言,不管使用什么样的convention来进行声明,实际在进行链接时还是按照__cdecl来进行处理的

对于上面提到的两种calling convention,只有在MSVC编译器下进行编译时才能正常工作,如果将代码移植到非win32平台上,需要添加一个头文件,内容如下

portable.h:

#ifndef _WIN32
#  define __cdecl    /* nothing */
#  define __stdcall  /* nothing */
#  define __fastcall /* nothing */
#endif /* _WIN32 */

这样就能编译通过,不会报错了

猜你喜欢

转载自blog.csdn.net/include_heqile/article/details/100101524