我在 MSVC 代码生成中偶然发现了一个奇怪的现象,涉及用作返回值的结构。考虑以下代码(此处为现场演示):
struct Result
{
uint64_t value;
};
Result makeResult(uint64_t value)
{
return { value };
}
struct ResultFactory
{
NOINLINE Result MakeResult(uint64_t value) const
{
return { value };
}
};
我们有一个结构体,它完美地满足了在 RAX 中返回的 x64-API 条件。而只要使用了free函数,就是这样的:
value$ = 8
Result makeResult(unsigned __int64) PROC ; makeResult, COMDAT
mov rax, rcx
ret 0
Result makeResult(unsigned __int64) ENDP ; makeResult
现在当我们查看成员函数时,它看起来略有不同:
Result ResultFactory::MakeResult(unsigned __int64)const PROC ; ResultFactory::MakeResult, COMDAT
mov QWORD PTR [rdx], r8
mov rax, rdx
ret 0
Result ResultFactory::MakeResult(unsigned __int64)const ENDP ; ResultFactory::MakeResult
在这里,编译器决定要求“Result”在第一个寄存器中传递一个引用(好吧,RDX/第二个,因为这就是当 RAX 无法返回时 MSVC 首先对成员函数所做的事情)。
为什么会这样呢?这有什么好的理由吗?这似乎不必要地悲观了代码生成,而且我真的认为它没有任何好处。让“RCX”始终是这种有意义的,但总是需要引用,即使对于原始结构也是如此?不幸的是,这也意味着使用成员函数和自由函数之间存在非常实际的区别,只要两者都可以内联即可。或者,如果使用成员函数,则返回原始类型并跨函数边界对其进行位转换可能会更快(是否所有问题都是另一个问题,但坦率地说,情况不应该如此) .
Clang/GCC 似乎做得“正确”。我不能 100% 确定这是否只是 MSVC 的怪癖,或者实际上是 x64-windows 调用约定(MSDN 并没有真正具体说明有关 c++ 的任何内容)。有人知道这里发生了什么事吗?
这是 Windows x64 ABI 所要求的。
非静态成员函数不能按值返回用户定义的类型。
只有static成员函数和全局函数可以按值返回用户定义类型。
返回值
用户定义类型可以通过全局函数和静态成员函数的值返回。要在 RAX 中按值返回用户定义类型,其长度必须为 1、2、4、8、16、32 或 64 位。它还必须没有用户定义的构造函数、析构函数或复制赋值运算符。它不能有私有或受保护的非静态数据成员,也不能有引用类型的非静态数据成员。它不能有基类或虚函数。并且,它只能具有也满足这些要求的数据成员。 (此定义本质上与 C++03 POD 类型相同。由于 C++11 标准中的定义已更改,因此我们不建议使用 std::is_pod 进行此测试。)
否则,调用者必须为返回值分配内存,并将指向它的指针作为第一个参数传递。然后将剩余参数向右移动一个参数。 RAX 中的被调用者必须返回相同的指针。
这不只是 C++ 方法在起作用吗?方法提供对调用对象的访问。在 C 中,您不能神奇地访问非全局的
struct
。在 C 中,要复制方法的功能,您可以执行以下操作:
struct Result
{
uint64_t value;
};
struct ResultFactory
{
int count;
};
Result makeResult(struct ResultFactory* factory, uint64_t value)
{
factory->count += 1;
return { value };
}
int main(int argv, char** argv)
{
struct ResultFactory factory = {0};
makeResult(&factory, 1);
}
就我们的目的而言,方法所做的就是将
object->set_val(value);
转换为 set_val(object, value);
。所以这个功能只是语法糖,但有隐式行为需要考虑。