tags:

views:

342

answers:

6

Consider this program

int main()
{
        float f = 11.22;
        double d = 44.55;
        int i,j;

        i = f;         //cast float to int
        j = d;         //cast double to int

        printf("i = %d, j = %d, f = %d, d = %d", i,j,f,d);
        //This prints the following:
        // i = 11, j = 44, f = -536870912, d = 1076261027

        return 0;
}

Can someone explain why the casting from double/float to int works correctly in the first case, and does not work when done in printf?
This program was compiled on gcc-4.1.2 on 32-bit linux machine.


EDIT: Zach's answer seems logical, i.e. use of format specifiers to figure out what to pop off the stack. However then consider this follow up question:

int main()
{

    char c = 'd';    // sizeof c is 1, however sizeof character literal
                     // 'd' is equal to sizeof(int) in ANSI C

    printf("lit = %c, lit = %d , c = %c, c = %d", 'd', 'd', c, c);
    //this prints: lit = d, lit = 100 , c = d, c = 100
    //how does printf here pop off the right number of bytes even when
    //the size represented by format specifiers doesn't actually match 
    //the size of the passed arguments(char(1 byte) & char_literal(4 bytes))    

 return 0;
}

How does this work?

+1  A: 

Because you are not using the float format specifier, try with:

printf("i = %d, j = %d, f = %f, d = %f", i,j,f,d);

Otherwise, if you want 4 ints you have to cast them before passing the argument to printf:

printf("i = %d, j = %d, f = %d, d = %d", i,j,(int)f,(int)d);
Jack
+11  A: 

The printf function uses the format specifiers to figure out what to pop off the stack. So when it sees %d, it pops off 4 bytes and interprets them as an int, which is wrong (the binary representation of (float)3.0 is not the same as (int)3).

You'll need to either use the %f format specifiers or cast the arguments to int. If you're using a new enough version of gcc, then turning on stronger warnings catches this sort of error:

$ gcc -Wall -Werror test.c
cc1: warnings being treated as errors
test.c: In function ‘main’:
test.c:10: error: implicit declaration of function ‘printf’
test.c:10: error: incompatible implicit declaration of built-in function ‘printf’
test.c:10: error: format ‘%d’ expects type ‘int’, but argument 4 has type ‘double’
test.c:10: error: format ‘%d’ expects type ‘int’, but argument 5 has type ‘double’

Response to the edited part of the question:

C's integer promotion rules say that all types smaller than int get promoted to int when passed as a vararg. So in your case, the 'd' is getting promoted to an int, then printf is popping off an int and casting to a char. The best reference I could find for this behavior was this blog entry.

Zach Hirsch
+1 I love stronger warnings.
Chris Lutz
I have a follow up question, please see the edited part of my question. Please comment if the question isn't clear...thanks
Sandip
It's true that `char` arguments get promoted to `int` when passed in the variable part of the argument list (this is what's happening when `c` is passed in the example), but character literal constants (like `'d'`) are already of type `int`. (Note also that in theory, under some compilers, `char` arguments could be promoted to `unsigned int` instead).
caf
good answer. This behavior is not particular to printf but hold for all variadic functions. How it works is well documented va_arg manpage, including promotion of chars to int. For an exemple of use you can see my answer in http://stackoverflow.com/questions/1688942
kriss
A: 

printf uses variable length argument lists, which means you need to provide the type information. You're providing the wrong information, so it gets confused. Jack provides the practical solution.

Matthew Flaschen
+3  A: 

Jack's answer explains how to fix your problem. I'm going to explain why you're getting your unexpected results. Your code is equivalent to:

float f = 11.22;
double d = 44.55;
int i,j,k,l;

i = (int) f;
j = (int) d;
k = *(int *) &f;         //cast float to int
l = *(int *) &d;         //cast double to int

printf("i = %d, j = %d, f = %d, d = %d", i,j,k,l);

The reason is that f and d are passed to printf as values, and then these values are interpreted as ints. This doesn't change the binary value, so the number displayed is the binary representation of a float or a double. The actual cast from float to int is much more complex in the generated assembly.

Chris Lutz
Might help him understand his output, but the code is not equivalent. You can't write equivalent code for the misinterpretation printf, or any variadic function, does while still calling the variadic function.
Roger Pate
My code does (for me, and probably for him) what his code is doing (for him). Both are undefined behavior, so neither one is guaranteed to do anything in particular.
Chris Lutz
+2  A: 

There's no such thing as "casting to int in printf". printf does not do and cannot do any casting. printf simply receives the raw data and interprets it as the type implied by the format specifier. If you pass it a double value and specify an int format specifier (like %d), printf will take that double value and blindly interpret it an an int. The results will be completely unpredictable (which is why doing this formally causes undefined behavior in C).

AndreyT
A: 

The reason your follow-up code works is because the character constant is promoted to an int before it is pushed onto the stack. So printf pops off 4 bytes for %c and for %d. In fact, character constants are of type int, not type char. C is strange that way.

MtnViewJohn