总结&备忘:__stdcall,__cdecl,extern "C"

原创 2016年03月09日 13:39:22

昨天给朋友解答了一下ESP异常的问题,才想起最近一年来都没怎么碰过C和WIN32开发了,怕再久一点再老一点就忘光了,还是得记录一下.

一、先解释一下__stdcall,__cdecl的作用

__stdcall,__cdecl,__fastcall,...这些是函数调用时参数传递方式,入栈顺序,怎么还原栈和函数在可链接模块中的命名的协议。VS编译环境里默认的协议是__stdcall

__stdcall协议为:

1.调用者把参数从右向左逐个入栈;

2.该栈由被调用者还原;

优点:为了节省汇编代码? 试想栈由调用者还原的话,那必定每次调用后都要写一份还原代码,调N次就会有N份还原栈代码

缺点:不支持可变参数. 试想若是可变参,那只有调用者知道压了多少个参数进栈(没另找方法告诉被调用者的情况下),这情况下被调用者怎么能知道要还原多大的栈空间呢

3.命名里追加参数信息

优点:实现函数重载; 

缺点:减慢编译速度...好吧...我偏执了,另一个缺点虽不是天生的,但现实存在,即不同C++编译器有不同的命名规范,导致互不兼容

__cdecl协议为:

1.调用者把参数从右向左逐个入栈;

2.该栈由调用者还原;

优点:支持可变参数; 

缺点:多次调用比stdcall更耗代码量)

3.只在函数名前加下划线'_'

优点:各编译器一致实现,相互兼容

缺点:不支持函数重载(姑且算上是缺点吧...)

二、ESP异常最常见的原因是在VS里用默认的__stdcall方式调用了用动态库里用__cdecl方式实现的函数

异常窗口信息:"The value of ESP was not properly saved across a function call..."大致意思系栈指针不正确,可能是由于不同函数调用协议混用导致的

有部分第三方C库用的是__cdecl协议编译的,你若在VS中动态加载时使用如下函数指针去指向该库中的函数:

typedef void (* Foo)(int a);

则会出现ESP异常.原因就是VS中默认为__stdcall协议,在这里,就是调用者使用__stdcall协议,被调用者使用__cdecl协议,则出现,调用者把参数压进栈后,直至函数调用完成,都没有进行栈的还原(即清理),因为被调用者是遵循__cdecl协议所以被调用者不还原栈,同时调用者又遵循__stdcall所以调用者也不还原栈.最终谁也没还原栈,导致栈的不平衡,触发了ESP检查异常.

故此,解决问题的方法就很明显了,函数指针加多__cdecl描述就可以了:

typedef void(_cdecl * FooI)(int a);


三、我朋友那竟然不是上述问题导致的

他是将第三方的源码和头文件拷进VS2013工程里直接编译的,头文件和实现文件里都没加任何描述符,那就都是默认一致的(这里为默认的__stdcall).

后来他自己发现他头文件里的声明是void fun(int a)

而实现文件里实现的却是void fun(int a,int b){}

把头文件里的声明修正为void fun(int a,int bI)后,正常运行.

我开始时就想,这报ESP也讲得通,调用者只压了一个int进栈,而被调用者还原栈时弹出了两个int,也会导致栈的不平衡,我就这么认为了

但过了几分钟回想了一遍...WTF!!!这不对啊,头文件声明错了应该是报"找不到该函数的实现"才对啊

难道我过去一年没弄VS,编译器发生了翻天覆地的变化???于是怀疑是C++11标准的新特性,简单翻了一下也查不到,只查到一个模板化的可变参数.想想也不可能是新特性,新特性是个会弹ESP异常的是什么鬼啊...或许是VS2013编译器里的BUG吧

身边现在没windows环境,暂不深入研究了

相关文章推荐

带你玩转Visual Studio——调用约定__cdecl、__stdcall和__fastcall

有一定C++开发经验的人一定对”__cdecl、__stdcall、__fastcall”肯定不陌生吧!但你真正理解了吗?是的,我曾在这采了无数个坑,栽了无数个跟头,终于忍无可忍要把它总结一下(虽然我...

编写和使用DLL时,常用的关键字 extern "C",__declspec,__cdecl,__stdcall

extern "C",__declspec,__cdecl,__stdcallextern "C" 是告诉编译器的编译方式;__cdecl和__stdcall是指函数的调用规范;__declspec一...

_stdcall,_cdecl与extern "C"

原文:http://www.cnblogs.com/hustcat/archive/2009/02/01/1382188.html  调用一个函数时,总是先把参数压入栈,然后通过call指令转移到被...
  • zwx5225
  • zwx5225
  • 2011年10月02日 20:04
  • 256

C/C++ 函数调用约定(__cdecl、__stdcall、__fastcall)

调用函数时,计算机常用栈来存放函数执行需要的参数,由于栈的空间大小是有限的,在windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好...

【C/C++】关于修饰函数关键字 __declspec,__cdecl,__stdcall,__declspec

_cdecl 是C Declaration的缩写,表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数不需要求调用者传递多少参数,调用者传递过多或者...

【C/C++】关于编译修饰符__stdcall、__cdecl和__fastcall 的异同点和应用场景

__stdcall、__cdecl和__fastcall是三种函数调用协议,函数调用协议会影响函数参数的入栈方式、栈内数据的清除方式、编译器函数名的修饰规则等。如下图所示,可以在IDE环境中设定所有函...

何时使用WINAPI,CALLBACK 以及_stdcall _cdecl _pascal 等的用法区别总结

#define   CALLBACK         __stdcall  #define   WINAPI             __stdcall  #define   WINAPIV ...

WINAPI CALLBACK _stdcall _cdecl _pascal _fastcall 简单总结

WINAPI和CALLBACK这两个宏有什么区别呢?_stdcall _cdecl _pascal _fastcall这些关键字是什么意思,有什么区别呢?首先看MSDN里给出的解释,不过有些语焉不详哦...
  • greeSdy
  • greeSdy
  • 2011年05月25日 09:29
  • 266

C++中_cdecl _stdcall _fastcall _thiscall函数调用总结(对照汇编代码)

首先,我们要学会在Visual Studio 2008中学会查看C++代码对应的汇编代码。给程序添加断点并开始调试程序后,对文件中间右键=》转到反汇编,即可看到汇编代码。 汇编代码用//注释,说明用/...

__stdcall,__cdcel,extern c 和导出函数名

无论c或c++都会对导出函数改名或不改名,无论你是静态还是动态调用一个导出函数,都可能碰上改名后导致的调用失败(甚至可能是调用约定不同而导致清理堆栈出错,造成崩溃),下面分析一下改名和调用约定之间的关...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:总结&备忘:__stdcall,__cdecl,extern "C"
举报原因:
原因补充:

(最多只允许输入30个字)