Environnment:
The conditions under which this occurs are:
Build command:
gcc floatTest.c -o floatTest -std=c99(arg c17 c11 c99 the result same as)
Code (also available on Godbolt):
#include <stdio.h>
int main()
{
float test = 5.0;
printf("first print test data is %x test %p\n", test, &test);
//fflush(stdout);
printf("second print test data is %x test %p\n", test, &test);
}
Output:
first print test data is 37df33f4 test 0x7ffd37df3508
second print test data is 37df33f4 test (nil)
I tried printing an integer type, and none of the mentioned issues occurred. The problem only arises when printing a floating-point type, and when the three conditions mentioned above are met, abnormal values are printed. I also tried using different compiler versions, but the results were consistently abnormal. Switching between different C standards yielded the same result. However, note that when I compiled and tested using Visual Studio 2022, the issue did not occur.
Why used printf the same variable addr twice result in different print values?
From X86-64 ABI (that I guess that you are using) https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf section 3.2.3 Parameter passing we know that:
int
type, that %x
expects, are also in INTEGER classPointer types and int
types use the same class. From page 20 of the document the class INTEGER types use the next available register of the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9 is used
and SSE class use different registers.
movss xmm0, dword ptr [rbp - 4]
cvtss2sd xmm0, xmm0
lea rdi, [rip + .L.str.2]
lea rsi, [rbp - 4]
/* note - rdx not assigned */
mov al, 1
call printf@PLT
The format string passed to printf
is a pointer. When printf
starts, it reads the format string from %rdi
register that points to the format string.
Then to output the %x
printf
reads the content of the next register in INTEGER class %rsi
and prints it formatted as an hex. The content of this register is populated with the next argument in INTEGER class, so the first printed value 37df33f4
is actually the address of &test
reinterpreted as an int
. You can confirm this by adding for example printf("%x\n", (int)(void*)&test);
to your code.
Then the %p
format specifier makes printf
read a value from the next INTEGER class register %rdx
. That register is not touched before the call to printf
, so %rdx
contains any garbage value that was there before the call to printf
.
On the beginning of your program, the %rdx
register happens to contain the value 0x7ffd37df3508
. That value most probably comes from the 3rd argument to main
. On POSIX systems, the C standard library calls the main with 3 arguments main(argv, argc, environ)
, and all these arguments happen to be in INTEGER class. The address to environ
is placed in the %rdx
register, and your main
code does not override it, so the first printf
call happens to print it. You can confirm it by printing extern char **environ; printf("%p\n", (void*)environ);
and comparing the result.
After the call to printf
, the %rdx
register happens to contain the value 0. That value is assigned somewhere inside printf
source code. You would have to go with a disassembler to get the exact location where it is assigned.
Note that this answer is solely very specific to X86-64 ABI. Also the compiler can detect that the code is invalid and decide to spawn nasal demons instead of producing any sane output.
#include <stdio.h>
extern char **environ;
int main() {
float test = 5.0;
printf("first print test data is %x test %p\n", test, (void*)&test);
printf("second print test data is %x test %p\n", test, (void*)&test);
printf("&test = %x\n", (int)(void*)&test);
printf("environ = %p\n", (void*)environ);
}
first print test data is 978a72ec test 0x7ffd978a7418
second print test data is 978a72ec test (nil)
&test = 978a72ec
environ = 0x7ffd978a7418
why this issue does not occur if the buffer is not flushed
Because fflush
happens to assign some different value to %rdx
register. You would have to go through a disassembler or debugger to find the location where this register happens to be assigned.