6

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

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

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

4

1 回答 1

7

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

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

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

或者当您需要清理调用者时,例如,支持可变参数。那么你需要__cdecl.

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

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

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


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

于 2016-05-27T15:32:28.193 回答