Reputation:
I think the title does not suit well for my question. (I appreciate it, if someone suggests an Edit)
I am learning C with "Learn C The Hard Way.". I am using printf
to output values using format specifiers. This is my code snippet:
#include <stdio.h>
int main()
{
int x = 10;
float y = 4.5;
char c = 'c';
printf("x=%d\n", x);
printf("y=%f\n", y);
printf("c=%c\n", c);
return 0;
}
This works as I expect it to. I wanted to test it's behavior when it comes to conversion. So everything was ok unless I made it to break by converting char
to float
by this line:
printf("c=%f\n", c);
Ok, I'm compiling it and this is the output:
~$ cc ex2.c -o ex2
ex2.c: In function ‘main’:
ex2.c:13:3: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
printf("c=%f\n", c);
^
The error clearly tells me that It cannot convert from int
to float
, But this does not prevent the compiler from making an object file, and the confusing part is here, where I run the object file:
~$ ./ex2
x=10
y=4.500000
c=c
c=4.500000
As you can see printf
prints the last float
value it printed before. I tested it with other values for y
and in each case it prints the value of y
for c
. Why this happen?
Upvotes: 0
Views: 189
Reputation: 30136
Here is a very general description, which may be slightly different depending on the compiler in use...
When printf("...",a,b,c)
is invoked:
The address of the string "..."
is pushed into the stack.
The values of each of the variables a
, b
, c
are pushed into the stack:
Integer values shorter than 4 bytes are expanded to 4 bytes when pushed into the stack.
Floating-point values shorter than 8 bytes are expanded to 8 bytes when pushed into the stack.
The Program Counter (or as some call it - Instruction Pointer) jumps to the address of function printf
in memory, and execution continues from there.
For every %
character in the string pointed by the first argument passed to function printf
, the function loads the corresponding argument from the stack, and then - based on the type specified after the %
character - computes the data to be printed.
When printf("%f",c)
is invoked:
The address of the string "%f"
is pushed into the stack.
The value of the variable c
is expanded to 4 bytes and pushed into the stack.
The Program Counter (or as some call it - Instruction Pointer) jumps to the address of function printf
in memory, and execution continues from there.
Function printf
sees %f
in the string pointed by the first argument, and loads 8 bytes of data from the stack. As you can probably understand, this yields "junk data" in the good scenario and a memory access violation in the bad scenario.
Upvotes: 0
Reputation: 61910
Your compiler is warning you about the undefined behaviour you have. Anything can happen. Anything from seeming to work to nasal demons. A good reference on the subject is What Every C Programmer Should Know About Undefined Behavior.
Normally, int can convert to double just fine:
int i = 10;
double d = i; //works fine
printf
is a special kind of function. Since it can take any number of arguments, the types have to match exactly. When given a char
, it is promoted to int
when passed in. printf
, however, uses the %f
you gave it to get a double
. That's not going to work.
Here is how one would implement their own variadic function, taken from here:
int add_nums(int count, ...)
{
int result = 0;
va_list args;
va_start(args, count);
for (int i = 0; i < count; ++i) {
result += va_arg(args, int);
}
va_end(args);
return result;
}
count
is the number of arguments that follow. There is no way for the function to know this without being told. printf
can deduce it from the format specifiers in the string.
The other relevant part is the loop. It will execute count
times. Each time, it uses va_arg
to get the next argument. Notice how it gives va_arg
the type. This type is assumed. The function needs to rely on the caller to pass in something that gets promoted to int
in order for the va_arg
call to work properly.
In the case of printf
, it has a defined list of format specifiers that each tell it which type to use. %d
is int
. %f
is double
. %c
is also int
because char
is promoted to int
, but printf
then needs to represent that integer as a character when forming output.
Thus, any function that takes variadic arguments needs some caller cooperation. Another thing that could go wrong is giving printf
too many format specifiers. It will blindly go and get the next argument, but there are no more arguments. Uh-oh.
If all of this isn't enough, the standard explicitly says for fprintf
(which it defines printf
in terms of) in C11 (N1570) §7.21.6.1/9:
If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.
All in all, thank your compiler for warning you when you are not cooperating with printf
. It can save you from some pretty bad results.
Upvotes: 1
Reputation: 780869
Since printf
is a varargs function, parameters cannot be converted automatically to the type expected by the function. When varargs functions are called, parameters undergo certain standard conversions, but these will not convert between different fundamental types, such as between integer and float. It's the programmer's responsibility to ensure that the type of each argument to printf
is appropriate for the corresponding format specifier. Some compilers will warn about mismatches because they do extra checking for printf
, but the language doesn't allow them to convert the type -- printf
is just a library function, calls to it must follow the same rules as any other function.
Upvotes: 0