Reputation: 45
case 1:
printf("%*d", 10, 5)
output :
_________5
(I am using _
to denote blank spaces )
case 2:
printf("%*d", 10.4, 5)
expected output:
______0005
But goes for infinite loop
why is this behavior being showed by %*d
for decimal "field width precision prefix" ?
Upvotes: 1
Views: 141
Reputation: 3812
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
Upvotes: 2
Reputation: 67546
Use -Wall -Wextra
to see more warnings. You will discover:
<source>:30:12: warning: field width specifier '*' expects argument of type 'int', but argument 2 has type 'double' [-Wformat=]
30 | printf("%*d", 10.4, 5);
| ~^~ ~~~~
| | |
| int double
It is undefined behaviour.
Now an example what is happening in the printf function:
int foo(const char *fmt, ...)
{
va_list va;
int retval = 0;
va_start(va, fmt);
retval = va_arg(va, int);
return retval;
}
unsigned bar(const char *fmt, ...)
{
va_list va;
unsigned retval = 0;
va_start(va, fmt);
retval = va_arg(va, unsigned);
return retval;
}
int main(void)
{
printf("as int %d\n", foo("", 10.4));
printf("as uint %u\n", bar("", 10.4));
}
And let's execute it: https://godbolt.org/z/EsMaM8EPs
Upvotes: 1
Reputation: 372814
When you pass 10.4 into printf
, it’s treated as a double
. However, when printf
sees the *
character, it tries to interpret the argument containing the number of characters to print as an int
. Despite the fact that you and I can intuitively see how to treat 10.4 as an integer by rounding down to 10, that’s not how C sees things. This results in what's called undefined behavior. On some platforms, C might treats the bytes of the double 10.4 as an integer, producing an absolutely colossal integer rather than the expected 10. On other platforms, it might read other data expecting to find an int
argument, but instead which holds some other unexpected value. In either case, the result is unlikely to be the nice "interpret the value as 10" that you expect it to be.
Upvotes: 1
Reputation: 198334
You told printf
using the *
format to expect a width integer in the argument list. You gave it a floating-point argument, 10.4
. C gets confused when it expects an integer and a float is there instead. You likely intended this:
printf("%*.*d", 10, 4, 5);
with width and precision being represented each by its own separate integral argument.
Upvotes: 4