仅限x86/64环境下的Linux实现:
代码:
#include <stdio.h> int main() { double x = -1.0; printf("%d
", x); return 0; }
32位模式下
对于printf来说,编译器就是把参数压栈,有几个参数就压几个,gcc编译完之后汇编是这样的:
541: ff 75 f4 pushl -0xc(%ebp) 544: ff 75 f0 pushl -0x10(%ebp) 547: 8d 90 18 e6 ff ff lea -0x19e8(%eax),%edx 54d: 52 push %edx 54e: 89 c3 mov %eax,%ebx 550: e8 5b fe ff ff call 3b0 <printf@plt>
因为x是double,是64位,所以541-544的代码,是压入x的值。在x86平台上,这个值(-1)是0xbff00000 00000000
所以上述代码使用gcc test.c -m32 -o test,那么输出的值应该恒定是0,因为0xbff0000000000000的低32位都是0
另外,如果代码改改,用%lld输出的话,恒定输出的是-4616189618054758400,等于-1的浮点在内存中的值。
64位模式下
64位于32位不同的在于,64位用寄存器传参,如果是整数类型的参数,使用rdi,rsi,rdx,rcx这些寄存器。如果是浮点类型的参数,那么使用xmm0,xmm1,...xmm7这些作为参数,不够用的时候才会考虑用栈(注:不同编译器的ABI不同,Win和Linux的就不一样)
反汇编效果
667: f2 0f 10 45 e8 movsd -0x18(%rbp),%xmm0 66c: 48 8d 3d a5 00 00 00 lea 0xa5(%rip),%rdi # 718 <_IO_stdin_used+0x8> 673: b8 01 00 00 00 mov $0x1,%eax 678: e8 a3 fe ff ff callq 520 <printf@plt>
第一个参数是rdi,这个没什么问题,第二个参数,对于printf来说,它看到的是%d,所以要从rsi寄存器里取值,而汇编代码里,没有对rsi初始化,第二个参数是放在了xmm0里。所以这就是为什么你每次运行的结果不一样了。
把代码简单的修改一下:
#include <stdio.h> int main() { double x = -1; int y = 100; printf("%d
", x, y); return 0; }
增加一行
int y = 100;
那么这个代码在64位下恒定输出的就是100
反汇编
66d: 89 d6 mov %edx,%esi 66f: 48 89 45 e8 mov %rax,-0x18(%rbp) 673: f2 0f 10 45 e8 movsd -0x18(%rbp),%xmm0 678: 48 8d 3d 99 00 00 00 lea 0x99(%rip),%rdi # 718 <_IO_stdin_used+0x8> 67f: b8 01 00 00 00 mov $0x1,%eax 684: e8 97 fe ff ff callq 520 <printf@plt>
可以看到esi(rsi),也就是第二个参数是被赋值了,那么这种改法,每次运行的结果就是确定的。
你的第二个问题是把整数作为参数传入,然后使用浮点模式显示
32位模式下
反汇编如下:
544: ff 75 e0 pushl -0x20(%ebp) 547: 8d 83 58 e6 ff ff lea -0x19a8(%ebx),%eax 54d: 50 push %eax 54e: e8 5d fe ff ff call 3b0 <printf@plt>
这个运行结果是不确定的,因为double浮点是64位的,而int在32位下是32位的,printf会尝试取得一个长度为64位的变量,但你只传入了32位,剩下的部分是栈上的垃圾数据(大概率是返回值之类的),这种情况下,每次调用的结果都会不一样。
如果要让它显示成一个固定的值,把int变量变成long long即可
int main() { long long i = -1; printf("%f
", i); return 0; }
每次固定显示-nan
64位模式下
反汇编如下
65c: 89 c6 mov %eax,%esi 65e: 48 8d 3d b3 00 00 00 lea 0xb3(%rip),%rdi # 718 <_IO_stdin_used+0x8> 665: b8 00 00 00 00 mov $0x0,%eax 66a: e8 b1 fe ff ff callq 520 <printf@plt>
这里,rsi就是那个整型变量,前面说了,如果用浮点数,编译器会使用xmm0~xmm7传参,这里并没有给xmm0赋值,所以理论上这里输出的内容是不确定的,但具体是不是不确定的,还要看GLIBC里的printf的实现。
如果要输出一个稳定的值,还有一个办法:
int main() { int i = 1; double r = 1234; printf("%f
", i, r); return 0; }
多传一个参数进去,printf就会利用这个参数了。
这个问题涉及的内容比较杂,如果要了解细节的话,需要学习以下知识点:
1. 不同编译器和操作系统的ABI以及寄存器的使用规则
2. 浮点数在内存的表示方式
3. LIBC里的printf的具体实现方式