【问题标题】:Why can't I specify the calling convention for a constructor(C++)?为什么我不能为构造函数(C++)指定调用约定?
【发布时间】:2014-05-10 01:27:42
【问题描述】:

在 Visual Studio 2013 中,存在一个新的调用约定 _vectorcall。它适用于可在 SSE 寄存器中传递的 SSE 数据类型。

您可以像这样指定成员函数的调用约定。

struct Vector{//a 16 byte aligned type
_m128i _vectorcall operator *(Vector a); 
};  

这可行,它可以编译,尽管有 16 个对齐要求,但类型可以按值传递。

另一方面,如果我尝试将它附加到任何构造函数(这似乎完全合乎逻辑),它会失败。

struct Vector
 _vectorcall Vector(SomeOtherTypeWith16Alignment a);
}; 

编译器吐出警告信息(我有警告为错误):

警告 C4166:构造函数/析构函数的非法调用约定。

强迫我把代码改成这样:

struct Vector{
   Vector(SomeOtherTypeWith16Alignment a); //fails to compile
}; 

这也无法编译,因为现在 SomeOtherTypeWith16Alignment 不能按值传递,因为 _vectorcall 没有在构造函数上启用。

所以我只好改成这个了。

struct Vector{
  Vector(const SomeOtherTypeWith16Alignment& a);
};

编译。但它不再使用 _vectorcall 并且可能不会像我希望的那样传递 SSE 寄存器中的数据......

那么基本上,为什么我不能指定构造函数使用的调用约定?

这可能是 Visual C++ 特定的(_vectorcall 肯定是)。我没有在其他编译器上试过这个--

【问题讨论】:

  • 除非您在构造函数中进行计算,否则您不需要将其参数传递到寄存器中。如果将参数值存储在成员变量中,则无论如何它都不会存储在寄存器中。
  • 嗨约阿希姆。在某些情况下,我正在构造函数中进行计算。例如,我有一个类型为 v4float,当用作寄存器时,它是一个 _m128 的包装器。还有另一种类型 v4float_memory 用于在寄存器之外进行存储。 v4float_memory 的构造函数接受 v4float。理想情况下,这应该在寄存器中传递,因为它在传入的数据上调用 _mm_store_ps。
  • 并且编译器不会在需要时将其优化到寄存器中?你检查过生成的汇编代码吗?
  • 我通过 __forceinlining 解决这个问题,希望编译器能够弄清楚实际上不是通过引用传递东西..
  • 即使找到了解决方案,最初的问题也是有效的,我认为它仍然需要一个答案:“为什么不能为构造函数提及调用约定?”。

标签: c++ visual-c++ visual-studio-2013 simd calling-convention


【解决方案1】:

这与其说是一个答案,不如说是对这种情况的观察集合。这确实是一个有趣的问题,因为似乎确实有某种东西(我不知道是什么)使构造​​函数(和析构函数)从根本上与调用约定不兼容。

首先,不要在C++标准中寻找答案,因为标准不关心调用约定,其实标准中唯一提到的就是作为一个例子该文档的范围(即“实现定义的”)。因此,该标准并未“禁止”为构造函数(或析构函数)指定或使用不同的调用约定。

通过一些研究和测试,不同的主要编译器的情况似乎如下(据我所知):

  • MSVC 允许您指定调用约定,但对于“stdcall”以外的任何内容都会忽略它(带有警告),它认为这意味着“thiscall”(stdcall 样式的 thiscall),无论如何这已经是默认值了。通过命令行选项(例如/Gz)更改默认调用约定不会影响非静态成员函数,例如构造函数。换句话说,没有办法改变构造函数的调用约定,无论你试图指定什么,它都会被忽略。
  • ICC 和 GCC 都忽略(带有警告)任何在构造函数上指定调用约定的尝试。
  • Clang 会忽略(带有警告)任何在构造函数上指定调用约定的尝试,除非它是“cdecl”,这也是默认值(cdecl 样式的 thiscall)。

换句话说,所有编译器似乎都非常固执,不允许在构造函数上使用除默认调用约定之外的任何其他类型的调用约定。核心问题是:为什么?

以下是对此的一些想法(推测)。

首先,传统上,调用约定更多地是关于二进制兼容性,而不是性能优化,即,您在链接和调用来自不同语言或编译器的函数时指定调用约定(例如,传统上在C、C++、Pascal 和 Fortran)。因此,这可以解释为什么某些 C++ 功能被排除在外。例如,如果您正在为 C++ 库创建一个接口(可能从不同的语言调用它),那么您要么必须回退到 C API,这意味着没有类、没有构造函数和没有成员函数,或者你有在具有固定调用约定的标准 ABI(例如,Itanium)的平台上公开 C++ API。从二进制兼容性的角度来看,这使得为构造函数指定调用约定完全没用。这可以解释为什么这被“忽略”(编译器未实现)作为低优先级功能。

这里可能涉及的另一个问题,特别是对于构造函数,如果你看一下Itanium ABI specification 的例子,你会发现构造函数调用非常特殊。特别是,虚拟继承需要传递一个临时虚拟表指针。这可以解释为什么编译器实现者很难为构造函数提供每种可能的调用约定的变体。换句话说,它不像只是添加一个“this”指针作为第一个“隐藏”参数然后应用标准 C 调用约定(就像它对普通非静态成员函数所做的那样)那么简单。我不认为为构造函数实现那些替代调用约定是不可能的,但是编译器供应商根本没有实现(还)会带来额外的麻烦。此外,给定对基类构造函数的调用,它还可能会产生诊断问题(验证基类构造函数调用约定是否与派生类调用约定兼容)。所以,这可能只是“太麻烦了;没有做”的情况。标准允许这样做,但没有一个编译器这样做,这一事实强烈表明可能是这种情况。

除此之外,我无法提供任何明确的答案来说明为什么这在某种程度上是不可能实现的。获得明确答案的唯一方法是,如果您找到一个直接参与实现其中一个编译器并且已经研究过尝试支持构造函数的不同调用约定的人。请注意,这样的人可能根本不存在(最好的办法可能是与 LLVM/Clang 开发人员社区联系,看看是否有人调查过这个问题)。

【讨论】:

    猜你喜欢
    • 2013-09-20
    • 2021-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-24
    相关资源
    最近更新 更多