C中的返回值优化和复制省略

Z b*_*son 56 c c++ struct return-value-optimization copy-elision

有些人不知道可以通过C中的值传递和返回结构.我的问题是关于编译器在C中返回结构时制作不必要的副本.诸如GCC之类的C编译器是使用返回值优化(RVO)优化还是仅仅是C++概念?我读过的关于RVO和copy elision的所有内容都与C++有关.

让我们考虑一个例子.我目前正在用C 实现一个双重数据类型(或者更确切地说是float-float,因为我觉得它很容易进行单元测试).请考虑以下代码.

typedef struct {
    float hi;
    float lo;
} doublefloat;

doublefloat quick_two_sum(float a, float b) {
    float s = a + b;
    float e = b - (s - a);
    return (doublefloat){s, e};
}
Run Code Online (Sandbox Code Playgroud)

编译器是否会复制doublefloat我返回的值,还是可以省略临时副本?

在C中命名的返回值优化(NRVO)怎么样?我有另一个功能

doublefloat df64_add(doublefloat a, doublefloat b) {
    doublefloat s, t;
    s = two_sum(a.hi, b.hi);
    t = two_sum(a.lo, b.lo);
    s.lo += t.hi;
    s = quick_two_sum(s.hi, s.lo);
    s.lo += t.lo;
    s = quick_two_sum(s.hi, s.lo);
    return s;
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我将返回一个命名结构.这种情况下的临时副本可以省略吗?

应该说这是C的一般问题,我在这里使用的代码示例仅仅是示例(当我优化它时,无论如何我将使用具有内在函数的SIMD).我知道我可以查看汇编输出以查看编译器的功能,但我认为这是一个有趣的问题.

Jer*_*fin 41

在C中的"as-if"规则中明确允许RVO/NRVO.

在C++中,您可以获得可观察的副作用,因为您已经重载了构造函数,析构函数和/或赋值运算符以提供这些副作用(例如,当其中一个操作发生时打印出来的东西),但是在C中你没有有能力重载这些运算符,内置的运算符没有可观察到的副作用.

如果没有重载它们,你就不会从copy elision中获得可观察到的副作用,因此没有什么可以阻止编译器这样做.

  • 函数中变量的地址和分配给外部的变量的地址可以是相同的,因为它们具有非重叠的生命周期:临时桥不能获取其地址,并且它共享另外两个变量的地址无法被检测到.我认为,这使得无法在标准下检测到它(在理论上):在实践中,如果函数中的变量和分配给外部的变量具有相同的地址,那么您正在目睹NRVO. (4认同)
  • 在gcc,g ++,clang,clang ++上对此进行测试,结果发现每个人都正确地执行NRVO,除了gcc`-xc`,当你有:`struct sf(){struct sx = g();时,会生成多余的副本.返回x; }` (3认同)
  • 我认为这可能是 LLVM 语义,模仿 C++ 语义,有时对于 C 程序可能会出现错误。但没人关心。 (2认同)

Ark*_*sis 34

它为C++所覆盖的原因是因为在C++中,RVO有副作用(即不调用临时对象的析构函数,也不调用生成对象的复制构造函数或赋值运算符).

在C中,没有可能的副作用,只有潜在的性能改进.我认为没有理由不能通过某些编译器执行这样的优化.至少,在标准中没有任何禁止它的东西.

无论如何,优化是编译器和优化级别相关的,所以我不打赌它用于关键代码路径,除非使用的编译器定义良好且预期不会改变(通常情况仍然如此).