Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ What actually happens in assembly when you return a struct from a function?

I'm trying to figure out what actually happens in C++ if you return a struct by value from a function, vs. return a pointer to the struct. How is a struct communicated when its sent by value if a function can only return a value that can fit in a register? (I read that somewhere.)

I tried testing it on Godbolt to see what it's doing. But I don't understand the assembly so that was a bit optimistic of me.

It does look to me without much assembly knowledge that the function is just changing some memory that exists before the function is called? So then the concept of returning something from a function is just an abstraction and the function is just setting some bytes in a memory location that already exists and then end jumping back to main()? In which case nothing is copied at all and the return is "free"?

Godbolt: Return int

Godbolt: Return struct{int int int}

like image 370
Alasdair Avatar asked Apr 15 '21 11:04

Alasdair


People also ask

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.

Can you return a struct from a function?

You can return a structure from a function (or use the = operator) without any problems. It's a well-defined part of the language. The only problem with struct b = a is that you didn't provide a complete type. struct MyObj b = a will work just fine.

How does return work in assembly?

In assembly language, the call instruction handles passing the return address for you, and ret handles using that address to return back to where you called the function from. The return value is the main method of transferring data back to the main program.

Does returning a struct copy it?

The lifetime of the mystruct object in your function does indeed end when you leave the function. However, you are passing the object by value in the return statement. This means that the object is copied out of the function into the calling function. The original object is gone, but the copy lives on.


2 Answers

How is a struct communicated when its sent by value if a function can only return a value that can fit in a register?

A function can return whatever is legal to return. However, only register-sized-or-smaller values can be used to implement the return statement by the convention of leaving a value behind in a single register, for obvious reasons. Some implementations allow for representing large data types using more than one register; of course, this means that the caller has to be written to expect to examine multiple registers to get the full return value.

"What happens" at the machine level is not specified by the language standard, and depends on the particular compiler, its optimization capabilities, details of the architecture, etc. However, the straightforward implementation on ordinary platforms is to have the caller reserve space on the stack (so that it lasts beyond the cleanup) and have the callee write the data there. Since the allocation is static, typically the required space can simply be taken into account when computing the size of the stack frame for the caller. The implementation might silently generate a pointer and pass it to the callee in a register; or it might arrange that every caller puts this reserved space in the same place in its stack frame, such that the callee can add an offset to the stack pointer to determine where to write; or it might do some other thing that I'm currently not creative enough to think of.

There are any number of ways to handle communication of information between functions at the machine level, depending on both the machine and the language (although we are usually talking about either C or C++ when we have these discussions, since all the other popular choices either run on a VM, are interpreted or have some other fancy thing going on). The general term you want to look into is Application Binary Interface, or ABI.

like image 60
Karl Knechtel Avatar answered Oct 17 '22 10:10

Karl Knechtel


So I spent hours playing with Godbolt's Compiler Explorer and reading up until I figured out the practical answer.

What I've gathered is this:

  1. If the value fits into a register, it's left in a register as the return value.
  2. If the value fits in 2 registers, it's left in 2 registers.
  3. If the value is larger than this, the caller reserves memory in its own stack and the function writes directly into the caller's stack.

Both G++ & Clang do the same, this is tested on x86_64.

like image 31
Alasdair Avatar answered Oct 17 '22 09:10

Alasdair