【问题标题】:What happens when a class/struct is returned WITHOUT RVO?当一个类/结构在没有 RVO 的情况下返回时会发生什么?
【发布时间】:2019-05-29 04:49:05
【问题描述】:

我了解 RVO 通常是可取的,但如果无法应用会发生什么情况?具体来说,生成的汇编代码如何将本地类/结构实例返回给它的调用者?

MyClass callee() {
    MyClass a;
    /* RVO is disabled */
    return a;
}

void caller() {
    /* RVO is disabled */
    MyClass b = no_rvo();
}

在这种情况下,堆栈是什么样的? caller()callee()是否分别在堆栈上为ab分配空间,然后将a复制到b?如果是这样,callee() 末尾的 RET 语句是否将堆栈指针完全递减到之前的值,还是在复制操作后释放了 a 的堆栈内存?

【问题讨论】:

  • 您可以在godbolt.org 上亲自尝试。使用__attribute__((noinline,noclone)),这样您就可以在启用优化的情况下进行编译,并且仍然可以看到函数调用。如果我能解决这个问题,我可能会在我的答案中添加一个示例。

标签: c++ assembly x86 calling-convention


【解决方案1】:

这取决于调用约定。但是所有 x86 32 位和 64 位调用约定都做出相同的选择,并将指向返回值的指针作为“隐藏”的第一个参数传递。

callee 仍然可以优化掉a(除非有其他东西阻止了这种情况的发生)并直接存储到该返回值指针中。

非平凡可复制类型可能需要在某些时候运行复制构造函数,并为a 运行析构函数。


但是回复:您的实际问题,启用优化后,调用者会将&b 作为返回值指针传递。

在禁用优化的情况下,我想我已经看到一些编译器为单独的临时返回值创建空间,然后从那里复制,这对于可简单复制的类型来说是非常多余的。

理论上,返回值指针可以指向堆栈内存以外的其他地方,例如如果分配给static MyClass arr[10]的元素。

但它不能指向任何callee 可以访问任何其他方式的东西,因为在 C++ 抽象机中,返回值是一个单独的对象,在函数返回之前没有任何东西可以指向.

【讨论】:

  • 另外,如果返回值是一个结构,它只包含一个寄存器大小的整数(或更小)IIRC,它只会被放入 x86_64 上的 rax 中。
  • @MatteoItalia:哦,是的,在 x86-64 System V 中,最多 16 个字节的小结构被打包到 RDX:RAX 中。或者如果它只是 FP 变量,则打包到低 8 个字节中每个 xmm1:xmm0。或完整 xmm0 中的 __m128。细节因调用约定而异。我忘记了不可复制的对象会发生什么以及它们是否得到特殊处理。
  • 最后一句话很有趣。 IIUC 你是说返回值指针不能指向一个全局变量,因为那时callee 不知道它们互为别名?但是如果编译器可以分析callee的定义,发现在修改返回值之前没有使用/读取该值呢?
  • @Acorn:哦,是的,IPA(程序间优化)可以允许许多通常不安全的事情,例如依靠函数实际上不会破坏易失性寄存器。 ABI / 调用约定必须在没有它的情况下工作,编译器在调用库中的函数时会这样做。 (或者没有链接时优化,另一个翻译单元中的任何功能)。但是,是的,您正确理解了问题;那会是那种会坏的东西。
猜你喜欢
  • 2016-12-31
  • 1970-01-01
  • 2018-12-21
  • 2015-10-04
  • 2012-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多