How does printf function work in C?

I ran into a problem while checking the printf function:

First I write the code as follows:

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

Output signal

enter image description here

And then I write the code:

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

Output signal

enter image description here

And then I write the code:

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

Output signal

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 printf() function? How does the character %f , %d ?

+5
source share
6 answers

Update: having done some more research on this issue, it seems that the differences between the memory representations of float and int not responsible for the behavior of the three programs.

I looked at the object code for the third program, and I found the reason for the odd behavior: floating-point arguments are sent to a different registry / stack than integers. And printf relies on this and looks for them in other places than those that the calling printf (i.e. the main method) places arguments.

Here is the corresponding disassembly of the third program (for 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 that the called user placed the %f arguments in the xmm0 / xmm1 / etc registers, and the behavior of the three programs is as follows:

  • in the first program, printf looks for the argument %f in the register xmm0, however, when we are at the beginning of the program, the register is clean and main placed a in eax , so xmm0 has a zero value, and this is what printf prints
  • in the second program, main correctly places b in xmm0 , and printf takes it from there, printing the correct value
  • in the third program, because b is printed first, the xmm0 register will hold this value, and since printf will not spoil the register when it calls a second time, when it selects again from xmm0 , which remained intact after the first printf call.

So, all about the caller / caller agreements, where integers and floats are sent by the caller, and where the caller is trying to pick them up from.


Original answer:. In the first program, you try to print a float, but you pass an int (char is a smaller int). Because ints and floats have different binary representations, int 97 (corresponding to the character โ€œaโ€) corresponds to a very small float: 1.36E-43, which prints as zero.

Here is the binary representation of 97 (the compiler extends a 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 floating point / double numbers, you can play with the online converter here , and you can see how the same binary number has different meanings when it is interpreted as int or as float.

+6
source

%f here is a replacement token for float.

To replace char you need %c .

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

+5
source

% f for Float% c for characters

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

According to the latest "C" standard, this behavior is undefined. Check 7.21.6.1 page 9 from a standard draft.

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

So, when it is said that undefined behavior is possible, and the behavior may differ from one compiler to another. "C" allows you to cut a sock with an ax, but that doesnโ€™t mean you should do this.

+1
source

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

If you use

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

You will receive a symbol.

So, if you change your first code to

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

You will get the result as

 a 
0
source

Difference between

 printf("%f\n, 97.0); 

and

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

the fact 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 char (a byte, but often actually an int, it depends on the implementation) and displays it (it displays a less significant byte if int).

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

If you compile gcc, use the -Wall parameter, which will give a warning if the %X format and parameter type do not match.

0
source

All Articles