ChatGPT解决这个技术问题 Extra ChatGPT

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

有些人不知道它是 possible to pass and return structs by value in C。我的问题是编译器在返回 C 中的结构时会生成不必要的副本。诸如 GCC 之类的 C 编译器是否使用 Return value optimization(RVO) 优化,或者这只是 C++ 的概念吗?我所读到的关于 RVO 和复制省略的所有内容都是关于 C++ 的。

让我们考虑一个例子。我目前正在 C 中实现 double-double data type(或者更确切地说是 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 和内在函数)。我知道我可以查看程序集输出以了解编译器的作用,但我认为这是一个有趣的问题。

@BaummitAugen,我也不确定是否应该使用 C++ 标签。但我想我在我的问题中明确表示它是关于 C 的。我希望 C++ 标签能吸引两种语言的专家。
@IvayloStrandjev 仍然是关于 C 的问题,标签适用于问题,不是吗?
@IvayloStrandjev 问题是:我们在 C 语言中有 RVO 吗?即使答案是“否”,C 标签也肯定适用,因为他问的是 C。
@jamesqf:显而易见的答案将涉及由 C 标准定义的 C,而不是您的个人心理模型。
@jamesqf,我认为在我的示例中按值返回这些结构更具可读性和逻辑性,并且不一定效率较低。我曾经和你一样想。这就是我问这个问题的原因。我的 C 心智模型正在演变。我现在提出反驳论点,即使用指针是过早的优化并且可能效率较低(恕我直言,您应该只优化编译器不能做的事情而不是它可以做的事情)。我还在考虑这个。

J
Jerry Coffin

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

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

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


函数中的变量地址和分配给外部变量的地址可以相同,因为它们具有不重叠的生命周期:临时桥无法获取其地址,并且无法检测到共享其他两个变量的地址.我认为,这使得在标准下(理论上)无法检测到它的发生:实际上,如果函数中的变量和分配给外部的变量具有相同的地址,那么您很可能正在目睹 NRVO。
在 gcc、g++、clang、clang++ 上对此进行测试,结果发现除了 gcc -xc 之外,每个人都正确执行 NRVO,当您有以下情况时,它会生成多余的副本:struct s f() { struct s x = g(); return x; }
@Yakk-AdamNevraumont 函数中变量的地址和分配给外部变量的地址可以相同,因为它们具有不重叠的生命周期 sret 没有- 重叠的生命周期或 Clang 搞砸了并使 NRVO 可见? godbolt.org/z/Ef4sxseTj
我认为这可能是 LLVM 语义,模仿 C++ 语义,有时可能对 C 程序行为不端。但没人在乎。
@LanguageLawyer 我不知道足够的 C 来回答这个问题。省略在 C++ 中是真实的;我知道在 C 中可以“好像”允许一些省略,但我不知道它在 C 中可以走多远。我必须在更深的层次上理解地址在 C(相对于 C++)中的含义比我现在做的。
A
Arkanosis

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

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

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