参考自: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 */
这样就能编译通过,不会报错了