为什么 MSVC 从不为成员函数返回 RAX 中的结构体?

问题描述 投票:0回答:2

我在 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++ 的任何内容)。有人知道这里发生了什么事吗?

c++ assembly visual-c++ x86-64 calling-convention
2个回答
1
投票

这是 Windows x64 ABI 所要求的。

非静态成员函数不能按值返回用户定义的类型。
只有static成员函数和全局函数可以按值返回用户定义类型。

x64 调用约定 - 返回值

返回值

用户定义类型可以通过全局函数和静态成员函数的值返回。要在 RAX 中按值返回用户定义类型,其长度必须为 1、2、4、8、16、32 或 64 位。它还必须没有用户定义的构造函数、析构函数或复制赋值运算符。它不能有私有或受保护的非静态数据成员,也不能有引用类型的非静态数据成员。它不能有基类或虚函数。并且,它只能具有也满足这些要求的数据成员。 (此定义本质上与 C++03 POD 类型相同。由于 C++11 标准中的定义已更改,因此我们不建议使用 std::is_pod 进行此测试。)
否则,调用者必须为返回值分配内存,并将指向它的指针作为第一个参数传递。然后将剩余参数向右移动一个参数。 RAX 中的被调用者必须返回相同的指针。


-2
投票

这不只是 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);
。所以这个功能只是语法糖,但有隐式行为需要考虑。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.