百科问答小站 logo
百科问答小站 font logo



将一个double型(int型)格式化输出为int型(double型)时发生了什么? 第1页

  

user avatar   bei-ji-85 网友的相关建议: 
      

仅限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的具体实现方式




  

相关话题

  学 C 语言用 break 和 continue 是不是坏的习惯? 
  在c语言当中,for循环,for(i=0;i<10;++i)与for(i=0;i++<10;)啥区别? 
  memcpy比循环赋值快吗?为什么? 
  C语言中, for 和 while 在汇编上有什么区别? 
  自学计算机,打印沙漏研究俩小时还有希望么? 
  c语言如何定义没有返回值的main函数? 
  该如何正确看待c中的字符串常量? 
  为什么现在国内各大高校仍选用谭浩强的《C 程序设计》为教材? 
  如果C语言程序在一台电脑上可以运行,到另外一台就运行出问题是什么原因? 
  为什么现在大厂很少招C语言开发了? 

前一个讨论
哪种操作系统可以兼容另一个操作系统的应用,但又不"基于"它?
下一个讨论
malloc一次性最大能申请多大内存空间?





© 2024-06-26 - tinynew.org. All Rights Reserved.
© 2024-06-26 - tinynew.org. 保留所有权利