Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do C compilers implement functions that return large structures?

The return value of a function is usually stored on the stack or in a register. But for a large structure, it has to be on the stack. How much copying has to happen in a real compiler for this code? Or is it optimized away?

For example:

struct Data {     unsigned values[256]; };  Data createData()  {     Data data;     // initialize data values...     return data; } 

(Assuming the function cannot be inlined..)

like image 585
Steve Hanov Avatar asked Jan 28 '10 15:01

Steve Hanov


People also ask

How are structure passing and returning implemented in C?

Q: How are structure passing and returning implemented? A: When structures are passed as arguments to functions, the entire structure is typically pushed on the stack, using as many words as are required. (Programmers often choose to use pointers to structures instead, precisely to avoid this overhead.)

How are structs returned C?

Use Standard Notation to Return struct From Function Now, functions in C can return the struct similar to the built-in data types. In the following example code, we implemented a clearMyStruct function that takes a pointer to the MyStruct object and returns the same object by value.

How are structs returned in assembly?

For struct return types, the typical solution is for the caller to provide space for the object and, say, pass a pointer to that space in EBX, or have a convention that this space is at the end of the stack before the function call, etc. The callee then writes the return value into that space.


2 Answers

None; no copies are done.

The address of the caller's Data return value is actually passed as a hidden argument to the function, and the createData function simply writes into the caller's stack frame.

This is known as the named return value optimisation. Also see the c++ faq on this topic.

commercial-grade C++ compilers implement return-by-value in a way that lets them eliminate the overhead, at least in simple cases

...

When yourCode() calls rbv(), the compiler secretly passes a pointer to the location where rbv() is supposed to construct the "returned" object.

You can demonstrate that this has been done by adding a destructor with a printf to your struct. The destructor should only be called once if this return-by-value optimisation is in operation, otherwise twice.

Also you can check the assembly to see that this happens:

Data createData()  {     Data data;     // initialize data values...     data.values[5] = 6;     return data; } 

here's the assembly:

__Z10createDatav: LFB2:         pushl   %ebp LCFI0:         movl    %esp, %ebp LCFI1:         subl    $1032, %esp LCFI2:         movl    8(%ebp), %eax         movl    $6, 20(%eax)         leave         ret     $4 LFE2: 

Curiously, it allocated enough space on the stack for the data item subl $1032, %esp, but note that it takes the first argument on the stack 8(%ebp) as the base address of the object, and then initialises element 6 of that item. Since we didn't specify any arguments to createData, this is curious until you realise this is the secret hidden pointer to the parent's version of Data.

like image 84
Alex Brown Avatar answered Oct 17 '22 07:10

Alex Brown


But for a large structure, it has to be on the heap stack.

Indeed so! A large structure declared as a local variable is allocated on the stack. Glad to have that cleared up.

As for avoiding copying, as others have noted:

  • Most calling conventions deal with "function returning struct" by passing an additional parameter that points the location in the caller's stack frame in which the struct should be placed. This is definitely a matter for the calling convention and not the language.

  • With this calling convention, it becomes possible for even a relatively simple compiler to notice when a code path is definitely going to return a struct, and for it to fix assignments to that struct's members so that they go directly into the caller's frame and don't have to be copied. The key is for the compiler to notice that all terminating code paths through the function return the same struct variable. If that's the case, the compiler can safely use the space in the caller's frame, eliminating the need for a copy at the point of return.

like image 45
Norman Ramsey Avatar answered Oct 17 '22 07:10

Norman Ramsey