Mr.CodeMonkey
Mr.CodeMonkey

Reputation: 121

How does the function printf works in 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?

Upvotes: 5

Views: 1016

Answers (6)

Cristik
Cristik

Reputation: 32923

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.

Upvotes: 7

anurag-jain
anurag-jain

Reputation: 1410

As per latest 'C' standard it's an undefined behavior. Check 7.21.6.1 pt 9 of from c standard draft.

If a conversion specification is invalid, the behavior is undefined.282) If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

So when things are said to have undefined behavior anything is possible and behavior may vary from one compiler to another. 'C' let's you cut your toe with an axe but that doesn't me you should do it.

Upvotes: 1

Naimah Nash
Naimah Nash

Reputation: 162

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.

Upvotes: 5

Déjà vu
Déjà vu

Reputation: 28850

Difference between

printf("%f\n, 97.0);

and

printf("%c\n, 'a');

is that the printf function reads its parameters from the stack based on the %X you give, and interprets them (for display) as such.

For %c printf expects a char as parameter, so it will read a char (a byte, but often actually a int, it's implementation dependant) and displays it (it displays the less significant byte if an int is provided).

For %f printf expects a float (which size in bytes is sizeof(float), usually 4 bytes on gcc / Intel processors).

If you compile with gcc use the -Wall option that would give a warning when the %X format and the type of the parameter do not match.

Upvotes: 0

M3ghana
M3ghana

Reputation: 1281

%f is for Float %c for characters

The 97 which you have got is the ASCII value for 'a'

Upvotes: 1

Arun A S
Arun A S

Reputation: 7026

%f is for float. You must use %c for characters.

If you use

    printf("a = %c\n", a);

You'll get the character.

So, if you change your first code to

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

You will get output as

a

Upvotes: 0

Related Questions