69

有些人不知道在 C 中可以按值传递和返回结构。我的问题是编译器在返回 C 中的结构时会制作不必要的副本。C 编译器(如 GCC)是否使用返回值优化(RVO)优化,或者这只是 C++ 的概念?我所读到的关于 RVO 和复制省略的所有内容都是关于 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};
}

编译器会制作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;
}

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

应该说这是 C 的一个普遍问题,我在这里使用的代码示例只是示例(当我优化它时,无论如何我都会使用带有内在函数的 SIMD)。我知道我可以查看程序集输出以了解编译器的作用,但我认为这是一个有趣的问题。

4

2 回答 2

50

根据 C 中的“as-if”规则,RVO/NRVO 显然是允许的。

在 C++ 中,您可以获得可观察到的副作用,因为您已经重载了构造函数、析构函数和/或赋值运算符以产生这些副作用(例如,当其中一个操作发生时打印出来),但在 C 中您不会有任何重载这些运算符的能力,并且内置的运算符没有明显的副作用。

如果不重载它们,您不会从复制省略中获得可观察到的副作用,因此没有任何东西可以阻止编译器这样做。

于 2015-05-04T15:46:35.327 回答
38

之所以对 C++ 进行大量介绍,是因为在 C++ 中,RVO 具有副作用(即,不调用临时对象的析构函数,也不调用结果对象的复制构造函数或赋值运算符)。

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

无论如何,优化取决于编译器和优化级别,所以我不会在关键代码路径上打赌,除非使用的编译器定义明确并且不会改变(这仍然经常发生)。

于 2015-05-04T15:45:59.287 回答