C++ ABI如何处理RVO和NRVO?

9

我对编译器和链接器如何处理函数调用者的要求因函数使用RVO或NRVO而不同感到困惑。

也许这是我的误解,但我的假设是通常情况下没有RVO或NRVO,

std::string s = get_string();

涉及将s从get_string的结果中移动构造,如果get_string不执行N?RVO,则如果get_string执行N?RVO,则调用代码不执行任何操作,并且s由函数get_string就地构造。
编辑: 以下是如果没有N?RVO,我认为get_string调用程序的操作方式:
1. 调用get_string() 2. get_string结果现在在堆栈上,调用程序使用它来构建s
现在使用RVO:
1. 调用get_string() 2. 当get_string完成时,堆栈上没有结果,get_string构造了s,调用方不需要做任何事情来构造s。

可能是[Move or Named Return Value Optimization (NRVO)?]的重复问题(https://dev59.com/TG025IYBdhLWcg3wEhnr)。 - Ripi2
1
不是真的,我知道RVO的概念是什么。我想知道在ABI中它是如何实现的,因为调用者需要知道被调用者是如何实现的(或者我错了吗?)。 - NoSenseEtAl
1
我不确定它与任何其他调用有什么不同。这是堆栈,因此链接器不关心。编译器只是在堆栈上一个接一个地布置变量。然后,所有变量访问都是从当前堆栈顶部的反向引用。在调用之前创建的变量将会稍微靠下一点。 - Galik
@Galik - 修改过的问题 - NoSenseEtAl
1个回答

16

无论如何,调用方都会为返回对象分配空间。从调用方的角度来看,函数是否使用RVO并不重要。

你还混淆了两个单独的复制省略。其中一个是RVO,它省略了从函数局部变量到返回值的复制,另一个是从函数返回值到正在初始化的对象的复制,这也经常被省略。

基本上,如果没有任何省略,可以将OP的调用视为类似于以下内容(忽略任何别名问题,所有这些内容实际上都是直接在汇编中实现的):

void get_string(void* retval)
{
    std::string ret;
    // do stuff to ret
    new(retval) std::string(std::move(ret));
}

char retval[sizeof(std::string)];
get_string(retval);
std::string s(std::move(*(string*)retval));

字符串ret会被复制(或者在这种情况下移动)两次:一次从retretval缓冲区,一次从retvals

现在,如果应用了NRVO,则只有get_string的定义会发生变化:

void get_string(void* retval)
{
    std::string& ret = *new(retval) std::string;
    // do stuff to ret
}

从调用者的角度看,没有任何改变。该函数只是直接将要返回的对象初始化到调用者为返回值分配的空间中。现在字符串只移动了一次:从retval移动到s

现在调用者也可以省略复制操作,因为不需要分配单独的返回值并将其复制到正在初始化的对象中:

char retval[sizeof(std::string)];
get_string(retval);
std::string& s(*(string*)retval);

这样,s 将直接通过 get_string 进行初始化,且不执行任何副本或移动操作。


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