JustAnotherDrone
JustAnotherDrone

Reputation: 88

Is it safe to pass a parameter to another function by reference (in C)?

Is the following code safe and portable?

void funcB(int *pA)
{
    // Do something w/ pA
} 
void funcA(int a)
{
    funcB(&a);
    // Do something else w/ a
}

My understanding is that on many systems, function parameters get placed in the CPU registers and not on the stack. So you're really passing the address of the register to the function, the contents of which will then be pushed onto to the stack so that the register can then be used for the parameter of funcB. So, in reality, when you dereference pA in funcB, you'll be getting the address pA instead the contents of a from funcA. Is my reasoning correct or am I totally off-base?

Upvotes: 2

Views: 210

Answers (3)

Nate Eldredge
Nate Eldredge

Reputation: 58473

Yes, perfectly safe and well-defined according to the C standard. A function parameter is an object and (conceptually) it has an address. It is safe for funcB to access that object via the pointer. Just keep in mind that, like any other local variable in funcA, its lifetime ends when funcA returns, so funcB should be careful about storing the pointer pA for later use.

Under the hood, even if a was passed to funcA in a register, if its address is needed then the compiler will need to copy that register onto the stack (called "spilling"), pass the address of that stack location, and use the value from that address for further operations on a. (Or, generate code that behaves "as if" this were done.)

Non-optimizing compilers will typically always spill the parameter to the stack, so that it truly has an address whether it's needed or not. But if the address isn't needed, the optimizer can strip this out and leave the parameter in its register.

Upvotes: 6

MikeCAT
MikeCAT

Reputation: 75062

It should be safe unless you are using a broken compiler. The compiler will allocate some region (typically) on the stack and pass its address, seeing that an address is required.

Here is one example on Compiler Explorer. I added a function to "do something" to prevent things from being optimized out.

void do_something(int*);

void funcB(int *pA)
{
    // Do something w/ pA
    do_something(pA);
} 
void funcA(int a)
{
    funcB(&a);
    // Do something else w/ a
}

Here is a result without optimization:

funcB:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $16, %rsp
        movq    %rdi, -8(%rbp)
        movq    -8(%rbp), %rax
        movq    %rax, %rdi
        call    do_something
        nop
        leave
        ret
funcA:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        leaq    -4(%rbp), %rax
        movq    %rax, %rdi
        call    funcB
        nop
        leave
        ret

Here is a result with -O2 optimization:

funcB:
        jmp     do_something
funcA:
        subq    $24, %rsp
        movl    %edi, 12(%rsp)
        leaq    12(%rsp), %rdi
        call    do_something
        addq    $24, %rsp
        ret

Now you can see the argument of funcA (%edi) is stored on the stack and its address is passed to funcB in both cases.

Upvotes: 2

ForceBru
ForceBru

Reputation: 44878

The compiler will handle register allocation for you and will make sure &a is the address of the data in a, not some random data. Also, "the address of the register" doesn't make much sense because a register has no address. In fact, the very expression &a will tell the compiler that you want a to be something that has an address, so it'll put the data in a into memory and then take an address of that data.

An example

This is what Clang generates for this C code:

funcB:
        sub     sp, sp, #4
        str     r0, [sp]
        add     sp, sp, #4
        bx      lr
funcA:
        push    {r11, lr}
        mov     r11, sp
        sub     sp, sp, #8

        ; The argument comes in register r0,
        ; but the compiler stores it into memory
        str     r0, [sp, #4]
        ; Then it puts the address of `a` (sp + 4)
        ; into `r0` and passes that to the other function
        add     r0, sp, #4
        bl      funcB
        mov     sp, r11
        pop     {r11, lr}
        bx      lr

Upvotes: 3

Related Questions