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

75
有些人可能不知道在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)。我知道我可以查看汇编输出以查看编译器的操作,但我仍然认为这是一个有趣的问题。

3
@BaummitAugen,我也不确定是否应该使用C++标签。但我认为在我的问题中已经很清楚地表明了它是关于C的。我希望使用C++标签能够吸引同时精通这两种语言的专家。 - Z boson
1
@IvayloStrandjev仍然是关于C语言的问题,标签适用于该问题,对吗? - BeyelerStudios
8
问题是:C语言中是否有RVO(返回值优化)?即使答案是“否”,也适用于C标签,因为他在询问C语言。 - Baum mit Augen
2
@jamesqf:显而易见的答案涉及到C是由C标准定义的,而不是你个人的心理模型。 - Jerry Coffin
2
@jamesqf,我认为在我的示例中通过值返回这些结构体更易读、更合乎逻辑,而且不一定效率更低。我曾经也像你一样想过。这就是我问这个问题的原因。我的C语言思维模式正在发展。现在我会反驳使用指针是一种过早优化,可能效率更低(在我看来,你应该只优化编译器无法完成的任务,而不是它可以完成的任务)。我还在考虑这个问题。 - Z boson
显示剩余9条评论
2个回答

54

RVO/NRVO显然符合C语言中的"as-if"规则。

在C++中,你可以通过重载构造函数、析构函数和/或赋值运算符来产生可观察的副作用(例如,在发生这些操作时打印某些内容),但是在C中,你没有能力重载这些运算符,并且内置的运算符没有可观察的副作用。

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


6
在函数中变量的地址和分配给外部的变量的地址可以相同,因为它们的生命周期不重叠:临时桥不能获取其地址,并且共享其他两个变量的地址也无法被检测到。我认为这使得在标准下(理论上)不可能检测到它发生的情况。实际上,如果函数中的变量和分配给外部的变量具有相同的地址,则很可能正在观察 NRVO。 - Yakk - Adam Nevraumont
7
在对gcc、g++、clang和clang++进行测试后,发现除了gcc的-xc选项外,其他编译器都可以正确执行NRVO(命名返回值优化)。当你有如下代码时,gcc -xc会生成多余的拷贝:struct s f() { struct s x = g(); return x; }。请注意,这里不提供解释。 - Peaker
@Yakk-AdamNevraumont 函数中变量的地址和分配给外部的变量的地址可以相同,因为它们具有不重叠的生命周期 sret 是否具有不重叠的生命周期,或者 Clang 出了问题,使 NRVO 可见?https://godbolt.org/z/Ef4sxseTj - Language Lawyer
2
我认为这可能是LLVM语义,模仿C++语义,并且有时对于C程序可能会表现不良。但是没有人在意。 - Language Lawyer
@LanguageLawyer 我不太懂C语言,无法回答那个问题。在C++中,省略是真实存在的;我知道在C语言中有一些省略可以被视为“as-if”允许,但我不知道它在C语言中能走多远。如果要深入了解地址在C语言中的含义(与C++不同),我需要比现在更深入地理解它。 - Yakk - Adam Nevraumont

38

在C++中,它被广泛涵盖的原因是因为RVO具有副作用(即不调用临时对象的析构函数以及结果对象的复制构造函数或赋值运算符)。

在C语言中,不存在可能的副作用,只有潜在的性能提升。我认为某些编译器可以执行这样的优化。至少,在标准中没有禁止这样做的内容。

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


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