1 区别

VC++的C/C++函数有两种基本的调用约定:__stdcall、__cdecl。它们有什么区别呢?请参考下表:

   

__stdcall

__cdecl

函数代码

C

int __stdcall addS(int a,int b)

{

return a + b;

}

int __cdecl addC(int a,int b)

{

return a + b;

}

ASM32

push    ebp

mov    ebp,esp

sub    esp,40h

push    ebx

push    esi

push    edi

lea    edi,[ebp-40h]

mov    ecx,10h

mov    eax,0CCCCCCCCh

rep    stos dword ptr [edi]

mov    eax,dword ptr [ebp+8]

add    eax,dword ptr [ebp+0Ch]

pop    edi

pop    esi

pop    ebx

mov    esp,ebp

pop    ebp

ret    8

push    ebp

mov    ebp,esp

sub    esp,40h

push    ebx

push    esi

push    edi

lea    edi,[ebp-40h]

mov    ecx,10h

mov    eax,0CCCCCCCCh

rep    stos dword ptr [edi]

mov    eax,dword ptr [ebp+8]

add    eax,dword ptr [ebp+0Ch]

pop    edi

pop    esi

pop    ebx

mov    esp,ebp

pop    ebp

ret

函数调用

C

addS(1,2);

addC(1,2);

ASM32

push    2

push    1

call    @ILT+85(addS)

push    2

push    1

call    @ILT+50(addC)

add    esp,8

说明:

1、函数 addS、addC 的调用约定分别是__stdcall、__cdecl,它们32位汇编代码的不同之处仅仅在于ret 8和ret;

2、addS(1,2)与addC(1,2)的32位汇编代码不同之处在于后者多了一个add esp,8;

3、寄存器esp保存着程序堆栈的栈顶地址

调用addS(1,2)的过程是这样的:push 2、push 1用来把参数压入堆栈,压入了两个4字节的int,所以esp的数值会减去8。然后call语句调用addS的汇编代码。形参a的数值是dword ptr [ebp+8],即push 1压入的1;形参b的数值是dword ptr [ebp+0Ch],即push 2压入的2。ebp其实是esp的初始值(mov ebp,esp),所以参数a、b是取自堆栈的。addS返回时调用的是ret 8,亦即返回时会将寄存器esp的数值加上8,完成堆栈的清除工作。

调用addC(1,2)的过程与addS(1,2)大致相同,不同之处在于ret不会修改寄存器esp,不会完成堆栈的清除工作。在执行add esp,8时,才会将esp寄存器的数值加上8,完成堆栈的清除工作。

简单的说,就是:__stdcall的函数在返回时会自动清除堆栈中的参数;__cdecl的函数在返回时不会自动清除堆栈中的参数,清除工作由调用者完成。

2 函数指针

再看看下面的例子:

函数调用

C

int(__stdcall*pfnS)(int,int)=&addS;

(*pfnS)(1,2);

int(__cdecl*pfnC)(int,int)=&addC;

(*pfnC)(1,2);

ASM32

mov    dword ptr [ebp-14h],offset @ILT+85(addS) (0040105a)

mov    esi,esp

push    2

push    1

call    dword ptr [ebp-14h]

cmp    esi,esp

call    _chkesp (00401f1e)

mov    dword ptr [ebp-18h],offset @ILT+50(addC) (00401037)

mov    esi,esp

push    2

push    1

call    dword ptr [ebp-18h]

add    esp,8

cmp    esi,esp

call    _chkesp (00401f1e)

调用(*pfnS)(1,2)比调用addS(1,2)多了如下代码:

mov    esi,esp            //保存寄存器esp的数值至寄存器esi

... ... ...

cmp    esi,esp            //查看寄存器esp的数值是否变化了

call    _chkesp (00401f1e)     //寄存器esp的数值变化了,提示出错

也就是说:通过函数指针调用函数,会检查寄存器esp的数值是否被正常恢复。

如果强制 pfnS 指向 addC,然后执行(*pfnS)(1,2);,会发生什么?请参考如下代码:

int(__stdcall*pfnS)(int,int)=(int(__stdcall*)(int,int))&addC;

(*pfnS)(1,2);

运行时会产生如下错误提示,说明寄存器 ESP 产生了错误:

stdcall与cdecl的区别的更多相关文章

  1. stdcall, cdecl, pascal 区别(转载)

    转载自:http://www.cnblogs.com/lidabo/archive/2012/11/21/2781484.html stdcall, cdecl, pascal 区别 这三个参数都是告 ...

  2. stdcall、cdecl详解(以及WINAPI和CALLBACK之类的宏对应什么)

    转自:http://blog.csdn.net/huanjieshuijing/article/details/5822942 对_stdcall 的理解在C语言中,假设我们有这样的一个函数:int ...

  3. stdcall、cdecl、fastcall、thiscall 、naked call的汇编详解

    函数调用规范   当高级语言函数被编译成机器码时,有一个问题就必须解决:因为CPU没有办法知道一个函数调用需要多少个.什么样的参数.即计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者 ...

  4. Java调用动态库方法说明-最详细

    Java不能直接调用由c或者c++写得dll(TF_ID.dll),所以只能采用jni得方法,一步一步生成符合规范得dll文件(假设叫FANGJIAN.dll),在FANGJIAN.dll这个文件里来 ...

  5. c++中的几种函数调用约定(转)

    C++中的函数调用约定(调用惯例)主要针对三个问题: 1.参数传递的方式(是否采用寄存器传递参数.采用哪个寄存器传递参数.参数压桟的顺序等): 参数的传递方式,最常见的是通过栈传递.函数的调用方将参数 ...

  6. Delphi中register, pascal, cdecl, stdcall, safecall(转)

    源:http://blog.sina.com.cn/s/blog_552c78120100hsr9.html 注: 使用错误,或者在该加的地方没有加,可能会出现"privileged ins ...

  7. Object Pascal 过程与函数

    过程与函数 过程与函数是实现一定功能的语句块,是程序中的特定功能单元.可以在程序的其他地方被调用,也可以进行递归调用.过程与函数的区别在于过程没有返回值,而函数有返回值. 1.过程与函数的定义 过程与 ...

  8. 【转】Delphi内嵌ASM简易教程

    Delphi内嵌ASM简易教程 作者:heiying2006-03-19 18:33分类:默认分类标签: 前言 Delphi作为一个快速高效的开发平台,使用的人越来越多,但熟悉在Delphi代码中嵌入 ...

  9. Delphi代码中嵌入ASM代码

    前言 Delphi作为一个快速高效的开发平台,使用的人越来越多,但熟悉在Delphi代码中嵌入ASM代码的程序员我想不多,因为这方面的资料太少了,另一方面,它还需要有基本的汇编语言知识,关於汇编语言的 ...

随机推荐

  1. java中局部变量和成员变量主要是他们作用域的区别

    成员变量个是类内部:局部变量是定义其的方法体内部(或者方法体内部的某一程序块内——大括号,主要看定义的位置).另外,成员变量可以不显式初始化,它们可以由系统设定默认值:局部变量没有默认值,所以必须设定 ...

  2. Duilib将UI资源文件打包到exe教程

    转载:http://www.voidcn.com/blog/w839687571/article/p-6001921.html 转载:http://www.voidcn.com/blog/x35698 ...

  3. Struts2的处理结果(一)——处理结果的配置

    Struts2的处理结果(一) --处理结果的配置 1.处理结果 在Action处理完用户请求之后,并不会直接生成响应,而是把一个字符串返回给Struts2框架,再由框架选择此字符串结果对应的物理视图 ...

  4. python 中类方法@classmethod

    classmethod是用来指定一个类的方法为类方法,没有此参数指定的类的方法为实例方法,使用方法如下: class C: @classmethod def f(cls, arg1, arg2, .. ...

  5. Hibernate+Struts2+jsp 修改用户信息

    在用户列表页面点击修改,进入修改页面 修改薪酬为555,点击提交,重新跳回该页面 修改成功 关键代码如下 基层的代码,这里增加了一个根据用户id查询的方法 dao层 //修改 public USer ...

  6. DIV与IDIV的用法

    DIV (unsigned divide) 无符号数除法 格式:DIV SRC 执行的操作: 字节操作:16位被除数在AX,8位除数为源操作数,结果的8位商在AL中,8位余数在AH中.表示为 (AL) ...

  7. python strip()函数

    转发:jihite-博客园-python strip()函数 函数原型 声明:s为字符串,rm为要删除的字符序列 s.strip(rm)        删除s字符串中开头.结尾处,位于 rm删除序列的 ...

  8. 使用Jmeter进行简单的http接口测试

    1.添加线程组:在“测试计划”上点击鼠标右键-->添加-->threads(Users)-->线程组,添加测试场景设置组件,接口测试中一般设置为1个“线程数”,根据测试数据的个数设定 ...

  9. 实现jQuery扩展总结

    开发自己需要的jQuery插件,看个示例说明<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"&qu ...

  10. cheat-linux命令行实用助记工具

    cheat究竟用来干嘛? 我们虽然能够使用man和--help来帮助我们查看命令使用方式,但是很多工程师都觉得,他们显然还不够man! 看看cheat是怎么man的 当我敲下cheat tar的时候, ...