Search code examples
creturn-valueinline-assemblycalling-convention

Is it possible within a function to get the memory address of the variable initialized by the return value?


I have been doing some research into inline assembly in C and how the call stack works but I have been unable to figure out if it's at all possible to retrieve the address of a variable that is requesting the return value of a function, from WITHIN the function.

int hypothetical_func(){

    /*...
    .. some assembly to get the address of 'int a' from the call stack?
    ...*/

    return 5;
}

int main(){
    int a = hypothetical_func();
}

Is this at all possible?


Solution

  • NO. int is returned in a register, and the callee has no involvement in what the caller does with that register after it returns. It might never be stored in memory.

    If the return-type wasn't int, but instead something large enough that the calling convention returned it by value, then hypothetical_func would have an address for an output. (Or a hypothetical (terrible) calling convention might return even int via hidden pointer instead of a register. Assuming the machine is a register machine like all real CPUs.)

    But that might just be a return-value temporary, not the actual LHS of an assignment. (Or initialization, which is close enough to the same thing in C, if not C++). Especially if the assignment is to a global or something. See What prevents the usage of a function argument as hidden pointer? for the case of *out = foo(); where T *out is a function arg. It's highly non-trivial to prove if/when it's safe to pass that function arg along as the return-value object for foo().

    And some compilers don't even try to optimize, and just make space on the stack for the return-value temporary and copy from there into the final object.

    And as @prl points out, the return-value might not even be the initializer for a variable. e.g. printf("%d\n", foo()); just passes on the return value to a function arg. Or foo(); discards the return value, not assigning it anywhere. (But if the calling convention specifies that the function returns by hidden pointer, the caller must pass a pointer to enough scratch space. The callee is still going to write its return value and needs to not segfault from a bad pointer or overwrite something else. That's an asm / calling-convention detail separate from the operation of the C abstract machine. Or I guess you could say the return-value object still exists, it's just not assigned anywhere.)


    Plus with inline assembly, you don't even have access to that. Unless you count writing a __attribute__((naked)) function where you still write the whole function inside an asm statement, and the compiler doesn't handle anything except the name-mangling of the function name. No prologue or epilogue, or abstracting away the calling convention with C variables for args and one that you return. (/grumble that C compilers can't create functions that return multiple separate values in multiple registers like you can in hand-written asm.)

    But even with hand-written asm, there's no way to do this for normal calling conventions on normal ISAs like x86 and ARM. The return-value object for int is just a register.