【文档翻译】__cdecl/__stdcall/__fastcall?解开神秘的调用约定!

这篇具有很好参考价值的文章主要介绍了【文档翻译】__cdecl/__stdcall/__fastcall?解开神秘的调用约定!。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文档译自 www.codeproject.com 的文章 "Calling Conventions Demystified",作者 Nemanja Trifunovic,原文参见此处


引言 - Introduction

在学习 Windows 编程的漫长、艰难而美妙的旅途中,你可能会对函数声明前出现的奇怪说明符感到好奇,比如 __cdecl__stdcall__fastcallWINAPI 等等。在阅读过 MSDN 或其他参考资料之后,你可能知道了这些说明符是用来为函数指定一种叫“调用约定”的东西。在这篇文章中,我会使用 Visual C++ 来向你解释不同的调用约定。我要强调的是,上面提到的说明符是微软特有的,如果你想编写可移植代码,就不应该使用它们。

那么,调用约定究竟是什么呢?当我们调用函数时,通常会将参数传递给它,并获得返回值。而调用约定就描述了参数是如何传递、值是如何从函数返回的。它还指定了函数名称的修饰方式。不过,编写优秀的 C/C++ 程序真的一定要了解调用约定吗?并不是。但是,它可能有助于调试。此外,如果要把 C/C++ 与汇编代码链接,那么这也有帮助。

要理解本文,你需要具备汇编编程的一些非常基本的知识。

无论使用哪种调用约定,都会发生以下情况:

  1. 所有参数都被扩展到 4 字节(除非特别说明,默认在 Win32 上),并放入内存的适当位置,这些位置通常在栈上。不过它们也可能被放在寄存器中,这便是通过调用约定指定的。
  2. 程序执行流会跳转到被调用函数的地址。
  3. 在函数内部,寄存器 ESI、EDI、EBX 和 EBP 的值被保存在栈上。执行这些操作的代码部分称为 function prolog,通常由编译器生成。
  4. 执行函数代码,并将返回值放入 EAX 寄存器中。
  5. 寄存器 ESI、EDI、EBX 和 EBP 的值从栈中恢复。执行此操作的代码段称为 function epilog,与 function prolog 一样,在大多数情况下,它由编译器生成。
  6. 参数从栈中移除。此操作称为清栈(stack cleanup),可以在被调用函数的内部执行,也可以由调用方执行,具体取决于所使用的调用约定。

作为调用约定的例子(不考虑 this),我们将使用一个简单的函数:

int sumExample (int a, int b)
{
    return a + b;
}

对这个函数的调用看起来像这样:

int c = sum (2, 3);

对于使用 __cdecl__stdcall__fastcall 的例子,我会把示例代码编译成 C 代码。本文后面提到的函数名修饰用的是 C 的修饰方法。C++ 的名称修饰方法超出了本文的讨论范围。


C 调用约定 - C calling convention (__cdecl)

这个约定是 C/C++ 的默认调用约定。如果项目被设置成使用其他的调用约定,我们也可以通过显式声明 __cdecl 来为某个函数指定:

int __cdecl sumExample (int a, int b);

__cdecl 调用约定的主要特点是:

  1. 参数将从右到左依次压入栈中。
  2. 由调用者执行清栈。
  3. 函数名用下划线字符 _ 作为前缀进行修饰。

现在,示例函数的调用看起来像这样:

; // 参数从右到左依次压入栈中
push        3    
push        2    

; // 调用函数
call        _sumExample 

; // 增加参数的总大小到 ESP 寄存器(即向高位移动栈指针),以此来清理堆栈
add         esp,8 

; // 将 EAX 寄存器中的返回值复制到局部变量 (int c)
mov         dword ptr [c],eax

被调用函数 sumExample 的内部如下所示:

; // function prolog
  push        ebp  
  mov         ebp,esp 
  sub         esp,0C0h 
  push        ebx  
  push        esi  
  push        edi  
  lea         edi,[ebp-0C0h] 
  mov         ecx,30h 
  mov         eax,0CCCCCCCCh 
  rep stos    dword ptr [edi] 
  
; //    return a + b;
  mov         eax,dword ptr [a] 
  add         eax,dword ptr [b] 

; // function epilog
  pop         edi  
  pop         esi  
  pop         ebx  
  mov         esp,ebp 
  pop         ebp  
  ret



标准调用约定 - Standard calling convention (__stdcall)

这个调用约定常常用在 Win32 API 的函数上。事实上,WINAPI 只是 __stdcall 的另一个名称。

#define WINAPI __stdcall

同样,可以为一个函数显式指定标准调用约定:

int __stdcall sumExample (int a, int b);

我们也可以使用编译器选项 /Gz 来给所有未显式声明约定的函数指定 __stdcall

__stdcall 调用约定的主要特点是:

  1. 参数将从右到左依次压入栈中。
  2. 由被调用的函数执行清栈。
  3. 函数名通过添加下划线 _@ 字符和所需的堆栈空间字节数来修饰。

调用示例如下:

; // 参数从右到左依次压入栈中
  push        3    
  push        2    
  
; // 调用函数
  call        _sumExample@8

; // 将 EAX 寄存器中的返回值复制到局部变量 (int c)
  mov         dword ptr [c],eax

函数如下所示:

; // 此处是 function prolog (和 __cdecl 的例子一样,略过)

; //    return a + b;
  mov         eax,dword ptr [a] 
  add         eax,dword ptr [b] 

; // 此处是 function epilog (和 __cdecl 的例子一样,略过)

; // 清栈并返回控制流
  ret         8

因为栈由被调用的函数清理,所以通常 __stdcall 调用约定创建的可执行文件比 __cdecl 要小。因为在 __cdecl 中,必须为每个函数调用生成清栈的代码。另一方面,参数数量可变的函数(如 printf())必须使用 __cdecl,因为只有调用者知道函数调用中的参数数量;所以,也只有调用方才能执行清栈。


Fast 调用约定 - Fast calling convention (__fastcall)

__fastcall指出,只要有可能,参数就应该放在寄存器中,而不是栈中。这减少了函数调用的成本,因为使用寄存器的操作比使用堆栈的操作要快。

我们可以显式声明 __fastcall 来使用约定,如下所示:

int __fastcall sumExample (int a, int b);

我们也可以使用编译器选项 /Gr 来给所有未显式声明约定的函数指定 __fastcall

__fastcall 的主要特点是:

  1. 需要 32 位大小(及以下)的前两个函数参数被放入寄存器 ECX 和 EDX。其余的从右向左压入堆栈。
  2. 被调用的函数负责从堆栈中弹出参数。
  3. 函数名通过在开头添加 @ 字符并附加 @ 和参数所需的字节数(十进制)来修饰。

注意:Microsoft 保留在未来的编译器版本中更改传递参数的寄存器的权利。

调用例子如下:

; // 将参数放入寄存器 EDX 和 ECX 中
  mov         edx,3 
  mov         ecx,2 
  
; // 调用函数
  call        @fastcallSum@8
  
; // 从寄存器 EAX 拷贝返回值到局部变量 (int c)  
  mov         dword ptr [c],eax

函数内部:

; // function prolog
  push        ebp  
  mov         ebp,esp 
  sub         esp,0D8h 
  push        ebx  
  push        esi  
  push        edi  
  push        ecx  
  lea         edi,[ebp-0D8h] 
  mov         ecx,36h 
  mov         eax,0CCCCCCCCh 
  rep stos    dword ptr [edi] 
  pop         ecx  
  mov         dword ptr [ebp-14h],edx 
  mov         dword ptr [ebp-8],ecx 
; // return a + b;
  mov         eax,dword ptr [a] 
  add         eax,dword ptr [b] 
;// function epilog  
  pop         edi  
  pop         esi  
  pop         ebx  
  mov         esp,ebp 
  pop         ebp  
  ret

这个调用约定究竟和 __cdecl__stdcall 相比有多快呢?你可以自己寻找答案。通过声明不同的约定,再比较执行时间看看吧。我没有发现 __fastcall 比其他调用约定更快,不过你可能会得出不同的结论。


Thiscall - Thiscall

Thiscall 是调用 C++ 类成员函数的默认调用约定(参数数量可变的除外)。

这种约定的主要特点是:

  1. 参数将从右到左依次压入栈中。this被放在 ECX 寄存器中。
  2. 由被调用的函数执行清栈。

这个调用约定的例子有点不同。首先,代码被编译为 C++,而不是 C。其次,我们用一个带有成员函数的结构体,而不是用自由函数。

struct CSum
{
    int sum ( int a, int b) {return a+b;}
};

函数调用的汇编代码如下所示:

push        3
push        2
lea         ecx,[sumObj]
call        ?sum@CSum@@QAEHHH@Z            ; CSum::sum
mov         dword ptr [s4],eax

函数内部如下所示:

push        ebp
mov         ebp,esp
sub         esp,0CCh
push        ebx
push        esi
push        edi
push        ecx
lea         edi,[ebp-0CCh]
mov         ecx,33h
mov         eax,0CCCCCCCCh
rep stos    dword ptr [edi]
pop         ecx
mov         dword ptr [ebp-8],ecx
mov         eax,dword ptr [a]
add         eax,dword ptr [b]
pop         edi
pop         esi
pop         ebx
mov         esp,ebp
pop         ebp
ret         8

如果我们有一个成员函数使用可变数量参数会发生什么?在这种情况下,会使用 __cdeclthis 最后被压入栈。


总结 - Conclusion

长话短说,我们总结调用约定之间的主要区别:

  • __cdecl 是 C 和 C++ 程序的默认调用约定。这种调用约定的优点是,它允许使用具有可变数量参数的函数。缺点是它会创建更大的可执行文件。
  • __stdcall 多用于 Win32 API 函数。它不允许函数具有可变数量的参数。
  • __fastcall 尝试将参数放在寄存器中,而不是堆栈中,从而使函数调用更快。
  • Thscall 调用约定是不使用可变参数的 C++ 成员函数使用的默认调用约定。

在大多数情况下,这就是你需要了解的关于调用约定的全部内容。文章来源地址https://www.toymoban.com/news/detail-747777.html

到了这里,关于【文档翻译】__cdecl/__stdcall/__fastcall?解开神秘的调用约定!的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • RISC-V基础之函数调用(五)函数递归调用及函数参数数量溢出(超出现有寄存器个数)约定(包含实例)

    首先先解释一下栈在函数调用中的作用,更详细的部分请参照考研复习之数据结构笔记(五)栈和队列(上)(包含栈的相关内容)_管二狗赶快去工作!的博客-CSDN博客 函数嵌套调用栈的作用是用来保存和恢复函数调用过程中的相关信息,如参数、局部变量、返回地址、上下

    2024年02月14日
    浏览(50)
  • C++动态库编程 | C++名称改编、标准C接口、extern “C”、函数调用约定以及def文件详解

    目录 1、导入导出声明 2、C++函数名称改编与extern \\\"C\\\" 3、函数调用约定与跨语言调用 3.1、函数调用约定 3.2、跨语言调用dll库接口 3.3、函数调用约定以哪个为准 4、def文件的使用 5、在C++程序中引用ffmpeg库中的头文件链接报错问题 6、最后 VC++常用功能开发汇总(专栏文章列表,

    2024年02月11日
    浏览(57)
  • Opentsdb官方优化文档 - 翻译

    Tuning — OpenTSDB 2.4 documentation As with any database there are many tuning parameters for OpenTSDB that can be used to improve write and read performance. Some of these options are specific to certain backends, others are global. 翻译: 与任何数据库一样,OpenTSDB有许多调优参数可用于提高写和读性能。其中一些选项是特定于

    2024年01月16日
    浏览(96)
  • MassTransit类库Saga文档翻译

    翻译自 Saga State Machines Saga State Machines(状态机)以前被称为Automatonymous,从v8开始被合并到masstrtransit代码库中。 Automatonymous是.Net的State Machines(状态机)类库,它提供了一种C#语法来定义State Machines,包括状态、事件和行为。MassTransit包括Automatonymous,并添加了实例存储、事件关联、

    2024年02月06日
    浏览(41)
  • 翻译docker官方文档(残缺版)

    The order of Dockerfile instructions matters. A Docker build consists of a series of ordered build instructions. Each instruction in a Dockerfile roughly translates to an image layer. The following diagram illustrates how a Dockerfile translates into a stack of layers in a container image. Dockerfile指令的顺序很重要。Docker构建由一系列有序的构

    2024年02月07日
    浏览(42)
  • 本地HTML文档批量翻译软件

    免费文档翻译软件将我们的本地文档进行批量翻译,支持多语言的翻译和批量导出。对于我们保存的excel、word、txt或是html文档都可以批量翻译,并自动发布到网站或者自媒体。免费文档翻译软件还支持对我们的英语网站进行内容采集,自动翻译保存到本地。   网站自媒体通

    2024年02月12日
    浏览(34)
  • MassTransit类库Saga模式实现文档翻译

    翻译自 Saga State Machines Saga State Machines(状态机)以前被称为Automatonymous,从v8开始被合并到masstrtransit代码库中。 Automatonymous是.Net的State Machines(状态机)类库,它提供了一种C#语法来定义State Machines,包括状态、事件和行为。MassTransit包括Automatonymous,并添加了实例存储、事件关联、

    2024年02月06日
    浏览(31)
  • 一款基于AIGC的文档翻译网站

    一款文字/文件翻译的网站,支持多个领域的翻译,支持常见的语言翻译(韩/日/法/英/俄/德…),最大百分比的保持原文排版(及个别除外基本100%还原)。 新用户注册就有100页的免费额度,每月系统还会随机赠送翻译额度,说实话这比好多的企业要好的多了,低至8毛钱一页,而且最贵

    2024年02月05日
    浏览(39)
  • slint1.32 官方文档翻译00

    来源于 Slint 1.3.2 Reference 主要用 有道翻译,个人参考用。翻译不妥的,请指正。 目录: Slint 1.3.2 Reference Slint 1.3.2参考 INTRODUCTION 介绍 Getting Started 开始 Supported Platforms 支持的平台 LANGUAGE REFERENCE 语言参考 Introduction 介绍 Concepts 概念 .slint File 文件 Positioning and Layout of Elements 元

    2024年01月16日
    浏览(48)
  • slint 1.3.2 官方文档翻译06

    SlintPad 基于官方文档的个人翻译,主要使用 有道翻译。 Debugging Techniques - Slint 1.3.2 Reference ADVANCED TOPICS 高级的主题-- Debugging Techniques 调试技术   On this page we share different techniques and tools we’ve built into Slint that help you track down different issues you may be running into, during the design and dev

    2024年01月18日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包