Reputation: 88
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
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
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
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.
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