这里写目录标题

  • Visual Studio调用约定 __cdecl、__stdcall和__fastcall
    • 什么是调用约定
    • 声明和定义处调用约定必须要相同
    • 函数的调用过程
    • __cdecl的特点
      • - 按从右至左的顺序压参数入栈。
      • - 由调用者把参数弹出栈。
    • __stdcall的特点
      • - 按从右至左的顺序压参数入栈。
      • - 由被调用者把参数弹出栈。
    • __fastcall的特点
    • __thiscall
      • -参数入栈:
      • -this指针入栈:
      • -栈恢复:
    • 总结
    • 我的理解:

Visual Studio调用约定 __cdecl、__stdcall和__fastcall

C++开发一定会遇到”__cdecl、__stdcall、__fastcall”,究竟什么意思呢?我是傻傻的分不清,现在开始认真学习一下吧。

以下内容转载于网友的文章。原文链接:https://blog.csdn.net/luoweifu/article/details/52425733

什么是调用约定

函数的调用约定,顾名思义就是对函数调用的一个约束和规定(规范),描述了函数参数怎么传递由谁清除堆栈的
它决定以下内容:
(1) 函数参数压栈顺序
(2) 由调用者还是被调用者参数弹出栈
(3) 以及产生函数修饰名的方法。

我们知道函数由以下几部分构成:返回值类型、函数名(参数列表),如:
【code1】

void function();
int add(int a, int b);

以上是大家所熟知的构成部分,其实函数的构成还有一部分,那就是调用约定。如下:
【code2】

void __cdecl function();
int __stdcall add(int a, int b);

上面的__cdecl和__stdcall就是调用约定,其中__cdecl是C和C++默认的调用约定,所以通常我们的代码都如 【code1】中那样定义,编译器默认会为我们使用__cdecl调用约定。

常见的调用约定有__cdecl、__stdcall、fastcall,应用最广泛的是__cdecl和__stdcall,下面我们会详细进行讲述。还有一些不常见的,如 __pascal、__thiscall、__vectorcall。

声明和定义处调用约定必须要相同

在VC++中,调用约定是函数类型的一部分,因此函数的声明定义处调用约定要相同,不能只在声明处有调用约定,而定义处没有或与声明不同。如下:
【code3】 错误的使用一:

int __stdcall add(int a, int b);       // 声明 有明确的调用约定  __stdcall
int add(int a, int b)                       // 定义 没有调用声明    默认是__cdecl
{return a + b;
}// 报错:
error C2373: ‘add’: redefinition; different type modifiers
error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’

【code4】 错误的使用二:

int  add(int a, int b);                          // 声明  没有明确的调用约定  默认是__cdecl  从报错信息中可以看出
int __stdcall add(int a, int b)         // 定义  有明确的调用约定
{return a + b;
}
// 报错
error C2373: ‘add’: redefinition; different type modifiers
error C2440: ‘initializing’: cannot convert from ‘int (__cdecl *)(int,int)’ to ‘int’

【code5】 错误的使用三:

int __stdcall add(int a, int b);                 // 声明 与 定义 调用约定 不同
int __cdecl add(int a, int b)
{return a + b;
}
// 报错
error C2373: ‘add’: redefinition; different type modifiers
error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’

【code6】 正确的用法

int __stdcall add(int a, int b);
int __stdcall add(int a, int b)
{return a + b;
}

函数的调用过程

要深入理解函数调用约定,你须要了解函数的调用过程和调用细节。
假设函数A调用函数B,我们称A函数为”调用者”,B函数为“被调用者”。如下面的代码,ShowResult为调用者,add为被调用者。

int add(int a, int b)
{return a + b;
}void ShowResult()
{std::cout << add(5, 10) << std::endl;
}

函数调用过程可以这么描述:

(1)先将调用者(A)的堆栈的基址(ebp)入栈,以保存之前任务的信息。
(2)然后将调用者(A)的栈顶指针(esp)的值赋给 ebp(基址),作为新的基址(即被调用者B的栈底)。
(3)然后在这个基址(被调用者B的栈底)上开辟(一般用sub指令)相应的空间用作被调用者B的栈空间。
(4)函数B返回后,从当前栈帧的ebp即恢复为调用者A的栈顶(esp),使栈顶恢复函数B被调用前的位置;然后调用者A再从恢复后的栈顶可弹出之前的ebp值(可以这么做是因为这个值在函数调用前一步被压入堆栈)。这样,ebp和esp就都恢复了调用函数B前的位置,也就是栈恢复函数B调用前的状态。
这个过程在AT&T汇编中通过两条指令完成,即:

   leaveret这两条指令更直白点就相当于:mov   %ebp , %esppop    %ebp

此部分内容参考: http://blog.csdn.net/zsy2020314/article/details/9429707.

__cdecl的特点

__cdecl 是 C Declaration 的缩写,表示 C 和 C++ 默认的函数调用约定。它是C/C++和MFCX的默认调用约定。

- 按从右至左的顺序压参数入栈。

- 由调用者把参数弹出栈。

切记:对于传送参数的内存栈是由调用者来维护的,返回值在EAX中。

因此对于像printf这样可变参数的函数必须用这种约定。
编译器在编译的时候对这种调用规则的函数生成修饰名的时候,在输出函数名前加上一个下划线前缀,格式为_function。如函数int add(int a, int b)的修饰名是_add。

(1). 为了验证参数是从右至左的顺序压栈的,我们可以看下面这段代码,Debug进行单步调试,可以看到我们的调用栈会先进入GetC(),再进入GetB(),最后进入GetA()。

(2). 第二点“调用者把参数弹出栈”,这是编译器的工作,暂时没办法验证。要深入了解这部分,需要学习汇编语言相关的知识。

(3). 函数的修饰名,这个可以通过对编译出的dll使用VS的 ”dumpbin /exports ProjectName.dll”命令进行查看(后面章节会进行详细介绍),或直接打开.obj文件查找对应的方法名(如搜索add)。

从代码和程序调试的层面考虑,参数的压栈顺序和栈的清理我们都不用太观注,因为这是编译器的决定的,我们改变不了。但第三点却常常困扰我们,因为如果不弄清楚这点,在多个库之间(如dll、lib、exe)相互调用依赖时常常出现莫名其妙的错误。这个我在后面章节会进行详细介绍。

__stdcall的特点

__stdcall是Standard Call的缩写,是C++的标准调用方式,当然这是微软定义的标准,__stdcall通常用于Win32 API中(可查看WINAPI的定义)。

- 按从右至左的顺序压参数入栈。

- 由被调用者把参数弹出栈。

切记:被调用者——即 函数自己在退出时清空堆栈,返回值在EAX中。

__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_function@number。

如函数int sub(int a, int b)的修饰名是_sub@8。 注意:__stdcall输出函数名的特点!!

__fastcall的特点

__fastcall调用的主要特点就是快,因为它是通过寄存器来传送参数的。

实际上__fastcall用ECX和EDX传送前两个DWORD或更小的参数,剩下的参数仍自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈

__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@function@number 。

如double multi(double a, double b)的修饰名是@multi@16。

__fastcall和__stdcall很象,唯一差别就是头两个参数通过寄存器传送。
注意通过寄存器传送的两个参数从左向右的,即第1个参数进ECX,第2个进EDX,其他参数从右向左的入栈,返回仍然通过EAX。
以上内容参考:http://www.3scard.com/index.php?m=blog&f=view&id=10

__thiscall

__thiscall是C++类成员函数缺省的调用约定,但它没有显示的声明形式。因为在C++类中,成员函数调用还有一个this指针参数,因此必须特殊处理,thiscall调用约定的特点:

-参数入栈:

参数从右向左入栈

-this指针入栈:

如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压栈后被压入栈。

-栈恢复:

对参数个数不定的,调用者清理栈,否则函数自己清理栈。

总结

这里主要总结一下_cdecl、_stdcall、__fastcall三者之间的区别:

要点 _cdecl _stdcall __fastcall
参数传递方式 右->左 右->左 左边开始的两个不大于4字节(DWORD)的参数分别放在ECX和EDX寄存器,其余的参数自右向左压栈传送
清理栈方 调用者清理 被调用函数清理 被调用函数清理
导适用场合 C/C++、MFC的默认方式; 可变参数的时候使用 Win API 要求速度快
C编译修饰约定 _functionname _functionname@number @functionname@number

我的理解:

第一条和第二条 参数的压栈顺序和栈的清理不用太观注,因为这是编译器的决定的,我们改变不了。
第三条“C编译修饰约定”非常重要,因为如果不弄清楚这点,在多个库之间(如dll、lib、exe)相互调用依赖时常常出现莫名其妙的错误。

Visual Studio调用约定 __cdecl、__stdcall和__fastcall相关推荐

  1. 【C 语言】动态库封装与设计 ( Windows 动态库简介 | Visual Studio 调用动态库 )

    文章目录 一.Windows 动态库简介 二.Visual Studio 调用动态库 一.Windows 动态库简介 在 C:\Windows\System32 目录中 , 存放着 Windows 中 ...

  2. 在matlab中如何打开示例程序,visual studio 调用 matlab实例

    续接上篇,本文将对如何通过visual studio调用matlab画图做出指导, 并给出实例. 代码部分: 首先在头文件补充engine #include"engine.h" 然 ...

  3. 【转】调用约定__cdecl、__stdcall和__fastcall的区别

    什么是调用约定 函数的调用约定,顾名思义就是对函数调用的一个约束和规定(规范),描述了函数参数是怎么传递和由谁清除堆栈的.它决定以下内容:(1)函数参数的压栈顺序,(2)由调用者还是被调用者把参数弹出 ...

  4. 函数的调用规则(__cdecl,__stdcall,__fastcall,__pascal)

    关于函数的调用规则(调用约定),大多数时候是不需要了解的,但是如果需要跨语言的编程,比如VC写的dll要delphi调用,则需要了解. microsoft的vc默认的是__cdecl方式,而windo ...

  5. (转)__declspec(dllimport)和__declspec(dllexport)的区别,以及有关c/c++调用约定

    DLL可以使用两种方法将公共符号导入到应用程序中或从 DLL 导出函数: 生成 DLL 时使用模块定义 (.DEF) 文件.  在主应用程序的函数定义中使用 __declspec(dllimport) ...

  6. 动态链接库、名字修饰约定、调用约定

    调用约定(Calling Convention)是指在程序设计语言中为了实现函数调用而建立的一种协议.这种协议规定了该语言的函数中的参数传送方式.参数是否可变和由谁来处理堆栈等问题.不同的语言定义了不 ...

  7. Visual Studio基本使用

    安装 软件下载关联地址(老版本要求登录,新版本是在线安装)VS2017 VS2019 Visual Studio 2022 路线图 | Microsoft Docs   (工具站MSDN, 我告诉你 ...

  8. Visual Studio C/C++ 编译器选项

    优化- /O1 最小化空间                          /O2 最大化速度 /Ob<n> 内联扩展(默认 n=0)               /Od 禁用优化(默认 ...

  9. 【原创+整理】简述何为调用约定,函数导出名以及extern C

    何为调用约定 调用约定指的是函数在调用时会按照不同规则,翻译成不同的汇编代码.这和参数的压栈顺序和栈的清理方式相关,也就是说不同的调用约定,这些方式会做相应改变.一般编译器是以默认的调用约定编译一份代 ...

  10. 2020-11-13(调用约定)

    a. x86 32位架构的调用约定 __cdecl:参数从右往左依次压入栈中,调用完毕,由调用者负责将这些压入的参数清理掉,返回值置于EAX中,绝大多数x86平台的c语言程序都在使用这种约定 __st ...

最新文章

  1. 大话设计模式C++版——装饰模式
  2. flask前端显示MySQL数据_flask怎样查询mysql并显示在页面上
  3. leetcode 566. Reshape the Matrix | 566. 重塑矩阵(Java)
  4. python 动态属性
  5. mysql怎样实现先判断后联合_MYSQ创建联合索引,字段的先后顺序,对查询的影响分析...
  6. mysql跨服务器查询
  7. 建模各阶段以及相关UML构造笔记
  8. tftp服务器上传文件至华三ac,ftp和tftp 上传文件到h3c交换机
  9. 跟我学Springboot开发后端管理系统1:概述
  10. 《守望先锋》架构设计与网络同步 -- GDC2017 精品分享实录
  11. c语言中的整型常量和实型常量
  12. 灰灰考研c语言讲义,【灰灰考研】操作系统复习全书.pdf
  13. Laravel使用Seeder自动填充数据
  14. 技术方案评审文档模版
  15. 【Element-ui】el-table大数据量渲染卡顿问题
  16. 什么叫死区时间_死区时间控制
  17. 二本考生计算机考研建议哪个学校,一般二本建议考研学校都有哪些
  18. Unix/Linux编程:进程间通信(IPC)总结
  19. CDN和CDN加速有什么关联
  20. 《论文写作》课程总结收获

热门文章

  1. 简信CRM:什么样的企业适合引入CRM管理系统?
  2. VOCALOID笔记
  3. 绩效管理-目标拆解技巧
  4. AR工业应用|企业中使用的7个增强现实创新案例|effiarAR工业云平台
  5. 设计模式实践-装饰器
  6. 15天助你掌握问卷统计与Spss实战
  7. 哲学家进餐问题 C++实现
  8. pta构造回文数C语言,【LeetCode】 #9:回文数 C语言
  9. Twaver-HTML5基础学习(26)背景
  10. 手把手教你用原生js写一个文字提示框