百科问答小站 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# 和 Java 哪个更像 C++? 
  你工作中最推荐的 C/C++ 程序库有哪些,为什么? 
  c语言如何定义没有返回值的main函数? 
  为什么 C 语言 sqrt 函数参数不支持 int? 
  英语不好能学C语言和C++么? 
  有人说C语言过时了,要学就学Python,怎么反驳他? 
  C、C++、Java、JavaScript、PHP、Python、Ruby 这些语言分别主要用来开发什么? 
  Python和C语言哪个更容易学,感觉学了C语言有点难,只学过C语言的大学生很迷茫? 
  C 语言如何不用 goto、多处 return 进行错误处理? 
  如何用C语言写一个简易计算器? 

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





© 2024-09-29 - tinynew.org. All Rights Reserved.
© 2024-09-29 - tinynew.org. 保留所有权利