void fun2(char *format, ...){
va_list arg_list;
va_start(arg_list, format);
vprintf(format, arg_list);
va_end(arg_list);
}
void fun1(char *format, ...){
fun2(format);
}
int main(){
fun1("test: %d", 100);
}
Output:
test: 100
https://onlinegdb.com/OfdDeSJg_
Does the above example have something wrong or not recommended?
I guess that when the fun2(format);
call is made, only the pointer to the first parameter (format
) is passed, is that correct?
When vprintf
in fun2
accesses the integer 100, where is this integer? in the stack reserved for fun1
, in the stack reserved for fun2
, in the stack reserved for vprintf
, or somewhere else?
If as I imagine only the pointer to the first parameter is passed to fun2
, does this mean that when the functions and macros called by fun2
access the integer 100 they are accessing the stack reserved for fun1
?
CodePudding user response:
They aren't forwarded. This code doesn't work, unless you are lucky.
If you are lucky, it's probably because the optimizer saw that format
stays in the same argument position (register or the stack), so fun1 doesn't actually have any "real" code in it (it doesn't move any arguments around), so it changed fun1 to a jump instruction that directly jumps to fun2 (a.k.a. tail-call optimization). Then since fun1 didn't mess with any of the arguments and didn't create a stack frame, they're still all in the right positions for calling a void (char*, ...)
function, where fun1 picks them up.
i.e. the assembly code is probably something like this:
// Pretend calling convention: format is in ecx, and varargs on the stack
fun2:
// actual code would go here
ret
fun1:
// jmp instruction doesn't affect the stack, nor ecx
// so fun2 receives exactly the same inputs as if main
// had directly called fun2
jmp fun2
main:
mov ecx, "test: %d"
push 100
call fun1
ret
You can't rely on it. If you want to reliably forward varargs you either change your function to use a va_list parameter (like vprintf) or you write your own assembly code.