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
?
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 (aka 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.
ie 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.
Yes, the example is doing something wrong. As a result, you are running into undefined behavior. As user253751's answer says, undefined behavior might happen to produce behavior that you want, but you cannot rely on it. user253751's answer gives a guess to why it might happen to produce the particular result you see in your particular case. But the same code might crash or produce wildly nonsensical results in another run or another environment.
Basically, when you called fun2(format)
, you passed 0 varargs. So you cannot safely read even a single vararg out of fun2
's va_list
(the variable arg_list
). But fun2
passes its va_list
to vprintf
, which, based on the format string, which contains one %d
placeholder, will try to read one int
from the va_list
. Reading more varargs than were passed causes undefined behavior.
In the section for va_arg
in the C99 standard, it says:
If there is no actual next argument, or if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined, except for the following cases:
- one type is a signed integer type, the other type is the corresponding unsigned integer type, and the value is representable in both types;
- one type is pointer to void and the other is a pointer to a character type.
In the section for fprintf
(which vprintf
and printf
refer to) in the C99 standard, it says:
If there are insufficient arguments for the format, the behavior is undefined.
Functions accepting varargs in C have no way of knowing how many or what types of varargs were passed. They depend on some kind of convention to determine when to stop reading and to know what types to read, and the caller has to adhere to the convention. For example, a function accepting a variable number of non-null pointers might need to be "terminated" with a null pointer at the end. The function depends on the caller to correctly pass a null pointer as the last argument, or the function will have undefined behavior. In another example, the printf
family of functions uses the number and types of placeholders in the format string to determine the number and types of varargs to read, and depends on the caller to correctly match the number and types of arguments to the placeholders in the format string.
So here, your fun2
has no way of knowing the number or types of varargs that were passed. But since fun2
passes its va_list
to vprintf
, its varargs must follow the same convention as printf
, ie the number and types of varargs must match the number and types of placeholders in the format string. However, in fun1
, it is passing a format string containing one %d
placeholder, but did not pass one int
in the varargs. Therefore it is violating the convention and causing undefined behavior.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.