Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the function printf works in C?

Tags:

c

I met a problem when I test the function printf:

First I write code like this:

int main(void)
{
    char a = 'a';
    printf("a = %f\n", a);
    return 0;
}

The output is

enter image description here

And then I write code:

int main(void)
{
    float b = 'a';
    printf("b = %f\n", b);
    return 0;
}

The output is

enter image description here

And then I write code:

int main(void)
{
    char a = 'a';
    float b = 'a';
    printf("b = %f\n", b);
    printf("a = %f\n", a);
    return 0;
}

The output is

enter image description here

So I am confused why in the first program a = 0.000000 and in the third program a = 97.000000?
How does the function printf() work?
How does the symbol %f, %d work?

like image 524
Mr.CodeMonkey Avatar asked Apr 16 '15 04:04

Mr.CodeMonkey


2 Answers

Update: after doing some more research on this, it seems that the differences between the float and int memory representations are not the ones responsible for the behaviour of the three programs.

I've looked at the object code for the third program and I found the cause of the odd behaviour: the floating point arguments are sent to other registry/stack than the integer ones. And printf relies on that and looks them up in other locations than the ones the callee of printf (i.e. the main method) places the arguments.

Here's the relevant disassembly of the the third program (for an x86_64 architecture):

0000000100000f18    leaq    0x71(%rip), %rdi        ## literal pool for: "b = %f\n"
0000000100000f1f    movsd   0x61(%rip), %xmm0      ## b variable gets sent to xmm0
0000000100000f27    movl    $0x0, -0x4(%rbp)
0000000100000f2e    movb    $0x61, -0x5(%rbp)      ## a variable gets placed on the callee stack
0000000100000f32    movsd   %xmm0, -0x10(%rbp)
0000000100000f37    movsd   -0x10(%rbp), %xmm0
0000000100000f3c    movb    $0x1, %al
0000000100000f3e    callq   0x100000f66             ## symbol stub for: _printf
0000000100000f43    leaq    0x4e(%rip), %rdi        ## literal pool for: "a = %f\n"
0000000100000f4a    movsbl  -0x5(%rbp), %esi
0000000100000f4e    movl    %eax, -0x14(%rbp)
0000000100000f51    movb    $0x0, %al
0000000100000f53    callq   0x100000f66             ## symbol stub for: _printf

And printf relies on this, it assumes the callee has placed %f arguments in the xmm0/xmm1/etc registers, and the behaviour of the three programs is like this:

  1. in the first program printf looks for the %f argument in the xmm0 register, however as we're at the start of the program, the register is clean and main has placed a into eax, thus xmm0 holds a zero value, and this is what printf prints
  2. in the second program main correctly places b in xmm0, and printf takes it from there, printing the correct value
  3. in the third program due to the fact that b is printed first, the xmm0 register will hold this value, and since printf doesn't mess with the register, when it's called the second time it fetches again from xmm0 that remained intact after the first printf call.

So it's all about caller/callee conventions on where integers and floats are being send by the caller and from where the callee tries to pick them up.


Original response: In the first program you are trying to print a float, but you pass an int (char is a smaller int). Due to the fact that ints and floats have different binary representations, the int 97 (corresponding to the character 'a') corresponds to a very small float: 1.36E-43, that gets printed as zero.

Here is the binary representation of 97 (the compiler expands any 1-byte char to a 4-byte argument when calling a function)
00000000 00000000 00000000 01100001

IEEE 754 is the standard format for binary representations of float/double numbers, you can play with an online converter here, and you can see how the same binary number has different values when its interpreted as an int or as a float.

like image 188
Cristik Avatar answered Oct 22 '22 14:10

Cristik


The %f here represents a replacement token for a float.

In order to replace a char you'd need %c.

Here is a list that tells you what is the appropriate replacement token for each type.

like image 5
Naimah Nash Avatar answered Oct 22 '22 15:10

Naimah Nash