How does function ACTUALLY return struct variable in C?

Solution 1:

Details vary widely by calling convention. Some ABIs have no calling convention for passing whole structures, in which case the compiler is free to do whatever it thinks makes sense.

Examples include:

  • Passing and returning the entire struct as a series of consecutive registers (often used with "small" structs)
  • Placing the entire struct as an argument block on the stack
  • Allocating an empty argument big enough to hold the struct, to be filled with a return value
  • Passing the (stack) address of the struct as an argument (as if the function was declared void function(struct inventory *))

Any of these implementations could conform to the C spec here. But, let's look at a specific implementation: the output from my GCC ARM cross-compiler.

Compiling the code you gave gives me this:

main:
    stmfd   sp!, {fp, lr}
    add fp, sp, #4
    sub sp, sp, #48
    sub r3, fp, #52
    mov r0, r3
    bl  function(PLT)

Destination operands are always on the left. You can see that the program reserves stack space, then passes the address of the stack space as r0 (the first argument in the ARM EABI calling convention). function takes no arguments, so this argument is clearly an artificial argument added by our compiler.

function looks like this:

function:
    stmfd   sp!, {r4, fp, lr}
    add fp, sp, #8
    sub sp, sp, #36
    str r0, [fp, #-40]
    ldr r3, .L6

        ...
    add r2, pc, r2
    mov r0, r2
    mov r1, r3
    bl  scanf(PLT)
    ldr r3, [fp, #-40]
    mov ip, r3
    sub r4, fp, #36
    ldmia   r4!, {r0, r1, r2, r3}
    stmia   ip!, {r0, r1, r2, r3}
    ldmia   r4, {r0, r1}
    stmia   ip, {r0, r1}
    ldr r0, [fp, #-40]
    sub sp, fp, #8
    ldmfd   sp!, {r4, fp, pc}

This code basically stashes the single argument in [fp, #-40], then later loads it and begins stashing data at the address it points to. At the end, it returns this pointer value in r0 again. Effectively, the compiler has made the function signature into

struct inventory *function(struct inventory *)

where the returned structure is allocated on the stack by the caller, passed in, and then returned.

Solution 2:

You're missing the most obvious thing there is to C's way of passing/returning things: everything is passed around by value, or at least: it behaves that way.

That is to say:

struct foo some_f( void )
{
    struct foo local = {
       .member = 123,
       .bar = 2.0
    };
    //some awsome code
    return local;
}

Will work, just fine. If the struct is small, then it's possible that this code will create a local struct variable, and return a copy of that struct to the caller.
In other cases, however, this code will roughly translate to :

void caller()
{
    struct foo hidden_stack_space;
    struct foo your_var = *(some_f(&hidden_stack_space));
}
//and the some_f function will behave as:
struct foo * some_f(struct foo * local)
{
    //works on local and
    return local;
}

Well, this isn't Exactly what happens all of the time, but it boils down to this, more or less. The result will be the same, but compilers may behave differently in this case.

Bottom line is: C returns by value, so your code works fine.
However, there are pitfalls:

struct foo
{
    int member1;
    char *str;
};
struct foo some_f()
{
    char bar[] = "foobar";
    struct foo local = {
        .member1 = 123,
        .str = &bar[0]
    };
    return local;
}

Is dangerous: the pointer assigned to local.str points to memory that will be released once the struct is returned. In that case, the problems you expected with this code are true: that memory is no more (or is not valid anymore).
Simply because a pointer is a variable whose value is the mem address, and that value is returned/assigned.

Solution 3:

A struct, at least a large one, will be allocated and returned on the stack, and will be popped off the stack (if at all) by the caller. The compiler will try to allocate it in the same spot where the caller is expecting to find this, but it will make a copy if that is not possible. It is possible, but not necessary that there is also a pointer to the struct, returned via registers.

Of course the details will vary depending on architecture.