为什么我不能为构造函数(C ++)指定调用约定?

时间:2014-03-30 20:09:43

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

在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当然是)。我没有在其他编译器上尝试过这个 -

1 个答案:

答案 0 :(得分:10)

这不是一个答案,而是更多关于这种情况的观察收集。这确实是一个有趣的问题,因为似乎有一些东西(我不知道是什么)使得构造函数(和析构函数)从根本上与调用约定不兼容。

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

从一些研究和测试来看,不同的主要编译器的情况似乎如下(尽我所能收集):

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

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

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

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

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

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