c++ - 何时使用 `__fastcall` 调用约定

标签 c++ c++builder vcl calling-convention c++builder-xe7

我们有很多用 C++ 编写的基于 VCL 的应用程序。所有 VCL 方法(在 __published 类修饰符下)都需要 __fastcall 调用约定。但是,无论出于何种原因,开发人员一直在将 __fastcall 添加到其他privateprotectedpublic 的非 VCL 函数。

基于 this article ,这对我来说毫无意义,因为它不必要地使代码复杂化,甚至可能会影响性能(虽然可能可以忽略不计)。尽管如此,在建议我们在某些地方删除它之后,我被告知我们一直都是这样做的,所以要保持一致,这只是一个风格问题。我认为如果没有必要,它实际上会让人们感到困惑,所以这是不好的做法。

我的问题是,什么时候使用__fastcall调用约定合适?

最佳答案

支持整个程序优化(也称为链接时代码生成)的优秀优化编译器不关心内部函数的调用约定*。它将使用在那种情况下最快/最好的任何调用约定,包括发明自定义调用约定或完全内联函数。

调用约定唯一重要的是构成公共(public) API 一部分的函数。在这种情况下,__fastcall 可能是一个糟糕的选择。使用 Windows 工具链广泛支持的更标准的调用约定,如 __cdecl__stdcall__fastcall 是互操作性特别差的选择,因为它从未标准化,因此不同供应商的实现方式不同。当您尝试将 DLL 与使用不同工具链(更不用说使用不同语言)编译的应用程序一起使用时,这将成为一场噩梦。

当然,当您使用记录为需要 __fastcall 约定的 VCL API 时除外。例如,文档说 VCL 类的成员函数使用 __fastcall 约定,因此您需要在所有覆盖中使用相同的调用约定。

或者当您需要清理调用方时,例如,以支持可变参数。然后你需要__cdecl

如果您确实想对内部函数(即那些不属于公共(public) API 的部分)使用特定的调用约定,您应该更愿意使用编译器开关来全局指定该函数。然后,这将指定调用约定用于其原型(prototype)未明确覆盖它的所有函数。这有几个优点。首先,它避免了一堆调用约定样板代码使您的代码困惑。其次,它允许您稍后轻松进行更改(例如,如果分析显示您最初选择的调用约定是优化器无法解决的瓶颈)。

有趣的是,__stdcall 优于 __cdecl 因为二进制大小的减少,被调用者而不是调用者调整堆栈(并且有被调用者比调用者少),但是正如您链接的文章提到的那样,__fastcall 可能并不总是比 __stdcall 快。这篇文章没有涉及任何技术细节,但问题基本上是 32 位 x86 上可用的寄存器数量极其有限。在寄存器中而不是在堆栈中传递值通常是性能上的胜利,但在某些情况下当函数很大并且用完寄存器时可能会变得悲观,迫使它将参数溢出回堆栈,做双重工作(这引起速度惩罚)并进一步膨胀代码(引起缓存惩罚,并间接导致速度惩罚)。如果值已经在堆栈中,但需要移动到寄存器中才能进行函数调用,这也是一种悲观情绪,阻碍了两个地方的优化潜力。

请注意,当您开始以 64 位 x86 架构为目标时,这一切都变得无关紧要。无论供应商如何,所有 Windows 应用程序的调用约定最终都已标准化。 x64 调用约定有点类似于 __fastcall,但由于可用寄存器的数量更多,因此在那里工作得更好。优化器不需要像在 x86-32 上那样经历那么多的扭曲来释放用于传递参数的寄存器。


* 请注意,当我在这里说“内部”函数时,我指的不是特定的访问修饰符,而是单个编译区内的函数和/或从未调用过的函数通过外部代码。

关于c++ - 何时使用 `__fastcall` 调用约定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37487011/

相关文章:

c++ - 获取 C 代码完全括号语法的解析器

c++ - 如何使用 C++ 使用字符串打印变量

c++ - C++ 中的校验和

c++ - w8004 编译器警告 BDS6 c/c++

c++ - C++ builder 与其他 RAD IDE 相比如何?

c++ - 使用 boost 标记宽字符串

Delphi: Hook ToggleSwitch 手动状态更改以避免 Click 调用

delphi - 是否记录了 Set 类型的星号运算符?

C++ Builder - 多线程数据库更新

c++ - 如何配置 VS 2017 中的 Windows 安装程序应安装哪个 .NET 版本