6
(gdb) disas func
Dump of assembler code for function func:
0x00000000004004b8 <func+0>:    push   %rbp
0x00000000004004b9 <func+1>:    mov    %rsp,%rbp
0x00000000004004bc <func+4>:    movl   $0x64,0xfffffffffffffff0(%rbp)
0x00000000004004c3 <func+11>:   movb   $0x61,0xfffffffffffffff4(%rbp)
0x00000000004004c7 <func+15>:   mov    0xfffffffffffffff0(%rbp),%rax
0x00000000004004cb <func+19>:   leaveq
0x00000000004004cc <func+20>:   retq
End of assembler dump.


t_test func()
{
    t_test t;
    t.i = 100;
    t.c = 'a';
    return t;
}

So it seems it's returning the local variable t,but is this kind of job guaranteed to work, isn't it supposed not to refer to any local variables when return??

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
new_perl
  • 7,345
  • 11
  • 42
  • 72
  • You're returning a structure in the sense that the type of the thing you're returning is a structure. But what you're actually returning is the value of a structure. So `int f() { ...` means that the type of thing `f` returns is `int`. And `return 5;` means that we're returning the *value* 5. Similarly `int q = 3; return q;` means that we're returning the *value* of `q`, which is 3. We aren't actually returning `q`. – David Schwartz Mar 30 '16 at 23:35

5 Answers5

7

In my experience, there is no standard way how C returns a structure. To be able to pass a struct, the compiler usually (invisibly to the user) passes a pointer to the struct, to which the function can copy the contents. How this pointer is passed (first or last on stack) is implementation dependent. Some compilers, like 32 bit MSVC++ return small structures in registers like EAX and EDX. Apparently, GCC returns such a struct in RAX, in 64 bit mode.

But, once again, there is no standard way how this is done. That is no problem when the rest of the code using a function is also compiled by the same compiler, but it is a problem if the function is an exported function of a DLL or a lib. I have been bitten by this a few times, when using such functions from a different language (Delphi) or from C with a different compiler. See this link too.

Community
  • 1
  • 1
Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • Is there any links saying this is implementation dependent behavior? – Je Rog Jul 18 '11 at 12:30
  • 1
    I know it is implementation dependent. Each implementation has its own way of doing this. I have been fighting this more than once, because it is not always easy to find out how to access a function (in a DLL) that returns a struct from a C or Delphi program. Especially if the func returns in registers, there is almost no direct way of handling this (except assembler). – Rudy Velthuis Jul 18 '11 at 14:08
  • @JeRog: The thing you're looking for is documentation on the *calling convention*. In this case, it's specified as part of the x86-64 System V ABI that GCC is targeting when compiling for x86-64 Linux, MacOS, or any non-Windows system. – Peter Cordes Oct 24 '20 at 04:23
6

RAX is big enough to hold the entire structure. At 0x00000000004004c7 you're loading the entire structure (with mov), not its address (you'd use lea instead).

The x86-64 System V ABI's calling convention returns C structs up to 16 bytes in RDX:RAX or RAX. C++ on x86-64: when are structs/classes passed and returned in registers?

For larger structs, the there's a "hidden" output pointer arg passed by the caller.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
BlackBear
  • 22,411
  • 10
  • 48
  • 86
  • what will it be like if rax is NOT big enough? – new_perl Jul 18 '11 at 10:14
  • 1
    @new_perl Create a larger struct and see for yourself. – Justin Jul 18 '11 at 10:48
  • it will copy the structure on the stack I guess. Try ;) – BlackBear Jul 18 '11 at 10:48
  • @Rudy: movs probably, but I'm not sure at all :P – BlackBear Jul 18 '11 at 11:25
  • I know how to copy a struct. But how does a function return stuff on the stack? The function doesn't know if the result should be assigned to a local or a global variable. That is why a pointer to the struct must be passed, and the return value must be copied to that place. Returning on the stack only works if the caller reserves extra memory on the stack and the function knows about this. Then the caller could perform the assignment. I don't know of any compiler that works that way. – Rudy Velthuis Jul 18 '11 at 17:52
3

It's not really standard at all how things are returned, but it's usually in RAX. In your example, assuming t_test::i and t_test::c are the only members of t_test and are at most 32-bits each, the entire structure can fit into a 64-bit register, so it just returns the values directly through RAX, and usually things that can fit into 2 registers are returned in RAX:RDX (or RDX:RAX, I forget the common order).

For larger than two registers, it generally involves a hidden pointer parameter being passed as the first parameter, which points to an object in the calling function (usually the one that directly gets assigned the return value). This object is then written to before returning from the called function (usually copied from the local structure used in the called function), and usually the same pointer that was passed is returned in RAX.

EAX/EDX can be substituted in for RAX/RDX on 32-bit x86 systems.

With conventions that pass the "this" pointer on the stack (like standard x86 GCC conventions), the return value pointer is usually passed as a hidden second parameter, instead of the first.

Kevin M
  • 331
  • 1
  • 6
  • So the hidden pointer points to the stack frame of called function,which is logically expired? – Je Rog Jul 18 '11 at 12:34
  • The _calling_ function allocates the space for it (usually it's already a local structure in that function, instead of something allocated specifically for the function call), which is then written to by the _called_ function (the one that is returning the big struct) before returning. On a side note, what you mentioned would still likely be "ok", since the stack frame of a function that JUST returned has not yet been overwritten. – Kevin M Jul 18 '11 at 12:37
  • What you describe may be true for some C and C++ compilers on Windows, but it is not even true for all. It is most definitely not universally that way (especially since not every processor has these registers). – Rudy Velthuis Jul 18 '11 at 12:45
  • I just tested this on gcc. I took a 4-byte structure. I created a function and created a local structure there and returned that in the struct created in main. Then looked at the assembly code generated. It all happened as you said. Probably the compiler used thiscall convention. – crisron Feb 01 '15 at 19:35
1

Your original code is returning a copy of the structure created in the function - because you're returning a structure type, not a pointer to a structure. What it looks like is that the entire structure is passed by value with rax. Generally speaking, the compiler can produce various assembly codes for this, depending and caller and callee behavior and calling convention.

The proper way to handle structure is to use them as out parameters:

void func(t_test* t)
{
    t->i = 100;
    t->c = 'a';
}
Eli Iser
  • 2,788
  • 1
  • 19
  • 29
  • Actually the proper way is to return by value. This method demands that anyone reading the code consider nonlocal effects in order to understand what's happening. Reentrancy and idempotence make it easier to prove your program is correct. Google "value semantics". – spraff Jul 18 '11 at 11:03
  • 1
    @spraff - I come from an embedded background, and for me returning structures by value is a bad habit. The same can be said for any implicit copying of parameters (arrays, classes, etc.). This is mainly due to performance reasons, and various bugs that can occur due to compiler error or generic-HW voodoo (lame excuse, but it happens more than I care for). – Eli Iser Jul 18 '11 at 12:48
  • Some compilers implement return-by-value in this mannar. Point is, you shouldn't be making the decision at this level. Return-by-value is more amenable to optimisation because copies can be elided and are potentially subject to certain other transformations. Prefer fixing your compiler to breaking your application. – spraff Jul 18 '11 at 14:28
  • @spraff - I see your point. However, due to the very static nature of embedded applications (at least in my field), the more common case is that you already have a memory allocated and you wish to populate it. For this scenario, nothing beats passing by-reference, I believe. – Eli Iser Jul 18 '11 at 15:04
  • Fair enough. Sometimes good design and good engineering aren't the same thing ;-) – spraff Jul 18 '11 at 16:59
  • @spraff - you've put it nicely, indeed :) – Eli Iser Jul 18 '11 at 19:01
  • The proper way to return a *small* struct (fits in one or two registers) is to return it by value, especially in a calling convention like x86-64 SysV that actually *does* return small structs in registers. If the caller doesn't actually want the struct values right away, then sure you could pass a pointer for the function to store them in memory (if it doesn't inline). – Peter Cordes Oct 24 '20 at 04:19
0

The stack pointer isn't changed in the start of the function, so the allocation of t_test isn't made within the function, and thus not freed by the function. How this is handled depends on the calling convention used. If you look at how the function is called it can be easier to see how it is done.

Anders Abel
  • 67,989
  • 17
  • 150
  • 217
  • I'm not sure if you can say that t_test is not allocated inside the function. It is not explicitly allocated, but e.g. Win64 calling convention requires the caller to reserve a special memory block to save registers to, before the call (i.e. in caller memory), and something like that seems to be used here (RBP-16 is the address used). IOW, it could be local after all, even if it is not explicitly allocated from the stack. – Rudy Velthuis Jul 18 '11 at 10:56
  • For the record, this is x86-64 System V, using the red-zone *below* RSP. The Windows x64 shadow space would be *above* RSP, but yes a function could use it the same way, as scratch space. – Peter Cordes Oct 24 '20 at 04:21