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

17

在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
}; 

由于现在构造函数上未启用 _vectorcall,所以也无法编译通过,因为无法按值传递 SomeOtherTypeWith16Alignment

所以我被迫将其更改为这样。

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

这段代码可以编译通过。但是它不再使用_vectorcall,也不太可能像我想的那样通过SSE寄存器传递数据...

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

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


编译器不会在需要时将其优化为寄存器吗?你有检查生成的汇编代码吗? - Some programmer dude
我通过__forceinlining来解决这个问题,希望编译器能够明白不要实际通过引用传递东西。 - Froglegs
即使找到了围绕这个问题的解决方案,最初的问题仍然是有效的,我认为它仍然需要一个答案:“为什么不能为构造函数指定调用约定?” - bolov
+1 很好的问题。我们为什么要阻止别人提出这个问题呢? - David Heffernan
请在 Microsoft Connect 上提交一个 bug。 - James McNellis
显示剩余7条评论
1个回答

13
这不是一个答案,而是有关此情况的一些观察结果的集合。这确实是一个有趣的问题,因为似乎存在某种(我不知道是什么)使构造函数(和析构函数)与调用约定根本不兼容的东西。
首先,不要在C++标准中寻找答案,因为标准不关心调用约定,事实上,标准中唯一提到它的地方是作为超出该文档范围的示例(即,“实现定义”)。因此,标准并没有“禁止”为构造函数(或析构函数)指定或使用不同的调用约定的可能性。
从一些研究和测试来看,根据我所了解到的不同主要编译器的情况如下:
  • MSVC允许您指定调用约定,但除了"stdcall"之外的任何内容都会忽略它(并显示警告),因为它认为"stdcall"表示"thiscall"(stdcall样式的thiscall),而这已经是默认设置了。通过命令行选项(例如/Gz)更改默认调用约定不会影响非静态成员函数,如构造函数。换句话说,在构造函数上没有办法改变调用约定,无论您尝试指定什么,都将被忽略。
  • ICC和GCC都会忽略(并显示警告)在构造函数上指定调用约定的任何尝试。
  • Clang会忽略(并显示警告)在构造函数上指定调用约定的任何尝试,除非它是"cdecl",这也是默认值(cdecl样式的thiscall)。

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

以下是一些关于此问题的思考(推测)。

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

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

除此之外,我无法提供任何确切的答案,解释为什么这似乎是不可能实现的。唯一能得到确切答案的方式是找到一个直接参与其中一个编译器实现并尝试支持不同调用约定的人。请注意,这样的人可能甚至不存在(您最好检查LLVM / Clang开发者社区,看看是否有人研究过这个问题)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接