Suppose on my platform sizeof(int)==sizeof(void*)
and I have this code:
printf( \"%p\", rand() );
Will this be undefined behavior
To expand upon @larsman's answer (which says that since you violated a constraint, the behavior is undefined), here's an actual C implementation where sizeof(int) == sizeof(void*)
, yet the code is not equivalent to printf( "%p", (void*)rand() );
The Motorola 68000 processor has 16 registers which are used for general computation, but they are not equivalent. Eight of them (named a0
through a7
) are used for accessing memory (address registers) and the other eight (d0
through d7
) are used for arithmetic (data registers). A valid calling convention for this architecture would be
d0
and d1
; pass the rest on the stack.a0
and a1
; pass the rest on the stack.This is a perfectly legal calling convention, similar to calling conventions used by many modern processors.
For example, to call the function void foo(int i, void *p)
, you would pass i
in d0
and p
in a0
.
Note that to call the function void bar(void *p, int i)
, you would also pass i
in d0
and p
in a0
.
Under these rules, printf("%p", rand())
would pass the format string in a0
and the random number parameter in d0
. On the other hand, printf("%p", (void*)rand())
would pass the format string in a0
and the random pointer parameter in a1
.
The va_list
structure would look like this:
struct va_list {
int d0;
int d1;
int a0;
int a1;
char *stackParameters;
int intsUsed;
int pointersUsed;
};
The first four members are initialized with the corresponding entry values of the registers. The stackParameters
points to the first stack-based parameters passed via the ...
, and the intsUsed
and pointersUsed
are initialized to the number of named parameters which are integers and pointers, respectively.
The va_arg
macro is a compiler intrinsic which generates different code based on the expected parameter type.
va_arg(ap, T)
expands to (T*)get_pointer_arg(&ap)
.va_arg(ap, T)
expands to (T)get_integer_arg(&ap)
.va_arg(ap, T)
expands to *(T*)get_other_arg(&ap, sizeof(T))
.The get_pointer_arg
function goes like this:
void *get_pointer_arg(va_list *ap)
{
void *p;
switch (ap->pointersUsed++) {
case 0: p = ap->a0; break;
case 1: p = ap->a1; break;
case 2: p = *(void**)get_other_arg(ap, sizeof(p)); break;
}
return p;
}
The get_integer_arg
function goes like this:
int get_integer_arg(va_list *ap)
{
int i;
switch (ap->intsUsed++) {
case 0: i = ap->d0; break;
case 1: i = ap->d1; break;
case 2: i = *(int*)get_other_arg(ap, sizeof(i)); break;
}
return i;
}
And the get_other_arg
function goes like this:
void *get_other_arg(va_list *ap, size_t size)
{
void *p = ap->stackParameters;
ap->stackParameters += ((size + 3) & ~3);
return p;
}
As noted earlier, calling printf("%p", rand())
would pass the format string in a0
and the random integer in d0
. But when the printf
function executes, it will see the %p
format and perform a va_arg(ap, void*)
, which will use get_pointer_arg
and read the parameter from a1
instead of d0
. Since a1
was not initialized, it contains garbage. The random number you generated is ignored.
Taking the example further, if you had printf("%p %i %s", rand(), 0, "hello");
this would be called as follows:
a0
= address of format string (first pointer parameter)a1
= address of string "hello"
(second pointer parameter)d0
= random number (first integer parameter)d1
= 0 (second integer parameter)When the printf
function executes, it reads the format string from a0
as expected. When it sees the %p
it will retrieve the pointer from a1
and print it, so you get the address of the string "hello"
. Then it will see the %i
and retrieve the parameter from d0
, so it prints a random number. Finally, it sees the %s
and retrieves the parameter from the stack. But you didn't pass any parameters on the stack! This will read undefined stack garbage, which will most likely crash your program when it tries to print it as if it were a string pointer.