74

Some people are not aware that it's possible to pass and return structs by value in C. My question is about the compiler making unnecessary copies when returning structs in C. Do C compilers such as GCC use Return value optimization(RVO) optimization or is this a C++ only concept? Everything I have read about RVO and copy elision is in regards to C++.

Let's consider an example. I'm currently implementing a double-double data type in C (or rather float-float to start with because I find it easy to unit test). Consider the following code.

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};
}

Will the compiler make a temporary copy of the doublefloat value I return or can the temporary copy be elided?

What about named return value optimization (NRVO) in C? I have another function

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;
}

In this case i'm returning a named struct. Can the temporary copy in this case be elided?

It should be stated that this is a general question for C and that the code examples I have used here are only examples (when I optimize this I will be using SIMD with intrinsics anyway). I'm aware that I could look at the assembly output to see what the compiler does but I think this is an interesting question nevertheless.

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
Z boson
  • 32,619
  • 11
  • 123
  • 226
  • 3
    @BaummitAugen, I was not sure either if I should use the C++ tag. But I think I made it clear in my question that it's about C. I was hoping the C++ tag would attract people who are experts in both languages. – Z boson May 04 '15 at 15:45
  • @BaummitAugen is there such concept in C at all. I have removed the `C` tag as it seems erreanous to me. – Ivaylo Strandjev May 04 '15 at 15:45
  • 1
    @IvayloStrandjev still the question is about C, the tag applies to the question, no? – BeyelerStudios May 04 '15 at 15:46
  • 8
    @IvayloStrandjev The question is: Do we have RVO in C? Even if the answer was "No", the C tag definitely applies because he asks about C. – Baum mit Augen May 04 '15 at 15:46
  • @Zboson What isn't clear is, what detector is that ZZ event from? – juanchopanza May 04 '15 at 15:48
  • @juanchopanza, haha! Good that you understand! I did my thesis on ZZ to four leptons. I think that one's from CDF but I did not do my thesis on CDF (I just found the picture from google). – Z boson May 04 '15 at 15:51
  • 1
    @Zboson Well I guess the tag is fine for the implicit and answered question *"Why do I only find information regarding C++?"*. – Baum mit Augen May 04 '15 at 15:53
  • `Can the temporary copy in this case be elided?` I wouldn't be the least bit surprised if this optimization existed in commercial C compilers dating back 20 or so years ago. It just got fancied up by C++ because, as the answer mentioned, C++ has side-effects to consider. – PaulMcKenzie May 04 '15 at 16:19
  • I think the real question is why you would do that, or want to do it, in the first place? Seems to break my whole mental model of how C works - that is, pretty much everything is a pointer - while adding a bunch of work 'under the hood' that might well have a negative impact on performance. – jamesqf May 04 '15 at 17:20
  • @jamesqf Wouldn't the question then rather be "if you do that, why are you using C?" ;) – hyde May 04 '15 at 21:26
  • 2
    @jamesqf: the obvious answer would involve something about C being defined by the C standard, not your personal mental model. – Jerry Coffin May 05 '15 at 01:49
  • @PaulMcKenzie, I expect that you're right about this optimization going back 20 years. However, sometimes in those last 20 years many people grew up on C++ instead of C and did not learn to think like C coders. – Z boson May 05 '15 at 07:32
  • 2
    @jamesqf, returning these structs by value in my examples I think is more readable and more logical and not necessarily less efficient. I used to think like you. That's the reason I asked this question. My mental model of C is evolving. I would now make the counter argument that using a pointer is a premature optimization and potentially less efficient (IMHO you should only optimize what the compiler can't do not what it can do). I'm still thinking about this. – Z boson May 05 '15 at 07:36
  • [Mission critical software has no heap allocation](http://archive.cotsjournalonline.com/articles/view/101217). C or not C. Also. One can try and compete with C++ or not GNUC, and he will be treated with respect. But he will never win that contract. – Chef Gladiator May 22 '19 at 08:31

2 Answers2

53

RVO/NRVO are clearly allowed under the "as-if" rule in C.

In C++ you can get observable side-effects because you've overloaded the constructor, destructor, and/or assignment operator to give those side effects (e.g., print something out when one of those operations happens), but in C you don't have any ability to overload those operators, and the built-in ones have no observable side effects.

Without overloading them, you get no observable side-effects from copy elision, and therefore nothing to stop a compiler from doing it.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • 6
    The address of the variable in the function, and the address of the variable assigned to outside, can be identical as they have non-overlapping lifetimes: the temporary bridge cannot have its address taken, and it sharing the other two variable's address cannot be detected. This, I think, makes it impossible to detect it happened (in theory) under the standard: in practice, if the variable in the function and the variable assigned to outside have the same address, odds are you are witnessing NRVO. – Yakk - Adam Nevraumont May 04 '15 at 15:57
  • 7
    Testing this out on gcc, g++, clang, clang++, it turns out that everyone does NRVO properly except gcc `-xc`, which generates superfluous copies when you have: `struct s f() { struct s x = g(); return x; }` – Peaker Nov 17 '15 at 06:17
  • @Yakk-AdamNevraumont _The address of the variable in the function, and the address of the variable assigned to outside, can be identical as they have non-overlapping lifetimes_ Do `s` and `ret` have non-overlapping lifetimes or Clang messed up and made NRVO visible? https://godbolt.org/z/Ef4sxseTj – Language Lawyer Jul 01 '22 at 11:05
  • 2
    I think this may be LLVM semantics, mimicking C++ one, and can sometimes misbehave for C programs. But nobody cares. – Language Lawyer Jul 01 '22 at 13:30
  • @LanguageLawyer I don't know enough C to answer that question. Elision is *real* in C++; I know that some elision can be "as-if" permitted in C, I do not know how far it can go in C. I'd have to understand what addresses mean in C (as opposed to C++) at a far deeper level than I do now. – Yakk - Adam Nevraumont Jul 01 '22 at 15:03
38

The reason why it's covered a lot for C++ is because in C++, RVO has side effects (ie. not calling the destructor of the temporary objects nor the copy constructor or assignment operator of the resulting objects).

In C, there's no possible side effect, only potential performance improvements. I see no reason such an optimization couldn't be performed by some compiler. At least, there is nothing that forbids it in the standard.

Anyway, optimization is compiler and optimization level-dependant, so I wouldn't bet on it for critical code paths, unless the compiler used is well defined and not expected to change (which is still often the case).

Arkanosis
  • 2,229
  • 1
  • 12
  • 18