The second line, printf("%*d",10.4,5)
leads to undefined behavior. The function printf
expects an int
but is given the double
10.4 instead.
The handling of the variable number of parameters of printf
, the format string and the corresponding types is complex, and goes deep into compiler construction. It's difficult to trace what happens exactly and can vary from compiler to compiler.
See here for the exact specifications of the printf
format specifiers.
Addition:
Let's see if we can trace what exactly happens in GNU's implementation of the standard library. First, looking at the source for printf
. It uses varargs
and a function called __vfprintf_internal
. The latter is defined here (line 1318). On line 1363, a sanity check is performed using a macro, but it only checks if the format string pointer is not NULL
. Line 1443:
int prec = -1; /* Precision of output; -1 means none specified. */
Line 1582, specifying the argument as an int
in case the *
modifier was used:
prec = va_arg (ap, int);
From here on, the precision is processed as an int
. Let's look at the implementation of va_arg
to see what happens if it is given a double
. It is part of the stdarg
header of GCC, see here, line 49:
#define va_arg(v,l) __builtin_va_arg(v,l)
And now it gets complex. __builtin_va_arg
isn't explicitly defined anyway, but is part of GCC's internal representation of the C language. See the parser file. That means that we cannot read concrete types in the source files anymore.
We can obtain some more information on the processing of varargs in the builtins.c
file. From now on I can only guess what happens. The processing appears to start in expand_builtin_va_start
which takes a tree
parameter and returns an rtx
(RTL Expression) object. This object is a constant and probably has the double
type mode. I'm assuming the compiler processes double
expression until it knows what (machine specific) bit values it has to write in the executable. Since, evidently, the floating point number is not truncated to an int
, I wouldn't be surprised if the value would actually correspond, and later will be interpreted as, a more or less random value (e.g. 77975616). It may be also conceivable that the memory of the program would misaligned when, e.g., the type of a double
(usually 8 bytes) is larger than an int
(usually 4 bytes). More on the implementation of varargs here.
Whatever sort-of random value the integer could take would be processed back in the process_arg(fspec)
back in vfprintf-internal.c
.
Additional curiosity:
If printf
is given a float
as specifier, even if it is explicitly cast, GCC will still give a warning that the value is a double
:
warning: field width specifier ‘*’ expects argument of type ‘int’, but argument 2 has type ‘double’ [-Wformat=]
10 | printf("%*d\n", (float) 12.3F, 5);
| ~^~ ~~~~~~~~~~~~~
| | |
| int double