关闭

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

标签: stdcallcdeclesp异常
258人阅读 评论(0) 收藏 举报
分类:

昨天给朋友解答了一下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环境,暂不深入研究了

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:8855次
    • 积分:211
    • 等级:
    • 排名:千里之外
    • 原创:12篇
    • 转载:0篇
    • 译文:0篇
    • 评论:6条
    最新评论