【问题标题】:C Floating point changes %d value every time executedC 浮点数每次执行都会改变 %d 个值
【发布时间】:2016-11-10 23:46:19
【问题描述】:

我尝试使用%d 打印浮点数(我知道不应该这样做。 但每次重新运行可执行文件时,它都会给出不同的值)
我的问题是:为什么每次打印的值都会改变? 我的系统:Ubuntu 14.04(64 位) 编译器:4.8.4 代码如下:

#include<stdio.h>

int main(){
  float b = 12.3456;

  printf("%d\n",b);

}

样本输出:

4bh1@mybox:~/C-fi$ ./test 
-1629995944
4bh1@mybox:~/C-fi$ ./test 
1147348376
4bh1@mybox:~/C-fi$ ./test 
-1746005432
4bh1@mybox:~/C-fi$ ./test 
510102216
4bh1@mybox:~/C-fi$ 

【问题讨论】:

  • 未定义行为的一个很好的例子——你还需要知道什么?
  • 我想知道为什么会这样?
  • 因为未定义?
  • 您使用的是什么平台/架构?
  • Ubuntu 14.04 64 位...

标签: c floating-point computer-science precision


【解决方案1】:

正如 cmets 中所指出的,这听起来像 未定义的行为,它解释了(嗯,有点)正在发生的事情。

如果您想读取浮点数的内部表示,或者您想对数字进行一些低级的修改,有一些简单的方法可以做到这一点,例如:

union {
    float f;
    int i;
} n;

n.f = 12.3456;
printf("%d\n", n.i); // should be the same number each time

请注意,这只适用于floatint 在您的系统上具有相同大小的情况,但应该对于大多数人来说都是如此。如果您想确定,可以添加assert(sizeof(float) == sizeof(int))

【讨论】:

  • 严格来说union hack 是不可移植的,至少因为不同的对齐规则,字段可能不会重叠。使用memcpy() 会更好。
  • @Serhio union 的开头从来没有任何填充,那么为什么成员不会重叠? ISO/IEC 9899:2011 § 6.7.2.1 (15) …There may be unnamed padding within a structure object, but not at its beginning. 诚然,这忽略了联合对象。
  • 关于我之前的评论,§6.5.3.4 (4) 关于sizeof:… When applied to an operand that has structure or union type, the result is the total number of bytes in such an object, including internal and trailing padding. 它没有提到前导填充,这表明它不存在。
【解决方案2】:

可能浮点值是通过 FPU 寄存器传递的,但 printf() 尝试从其他地方读取整数,它希望看到整数(堆栈或其他寄存器)。那个地方的内容是未指定的。严格地说(正如@dbush 提到的)您的代码会导致未定义的行为。

【讨论】:

  • 这与我知道的任何 ABI 都不对应,但是一些 ABI 传递浮点值以在寄存器中使用 %f 打印,而整数值将在寄存器中使用 %d 打印,这可能根据前面的代码(和 ASLR)解释我们看到的内容。
  • 例如对于 x64,msdn.microsoft.com/en-us/library/ms235286.aspx 表示“参数在寄存器 RCX、RDX、R8 和 R9 中传递。如果参数是浮点/双精度,它们在 XMM0L、XMM1L、XMM2L 和 XMM3L 中传递。”所以 printf 期望 XMM0L 中有一个浮点数,但该寄存器从未被您的代码设置。
  • @PascalCuoq amd64 的 SysV ABI 是这样的。
  • @FUZxxl 在这种情况下,整数将在寄存器中传递。前 6 个整数在寄存器中传递,第 7 个和更高的在堆栈上。
  • @PascalCuoq 感谢您提醒有关寄存器传递的参数。更正答案。
【解决方案3】:

printf 使用错误的格式说明符是undefined behavior 的经典示例。这意味着行为是不可预测的,并且不能依赖于从一次运行到下一次运行是一致的。它可能会崩溃,它可能会打印随机值,或者看起来可以工作。

实际发生的是相关编译器和架构的实现细节。但是除非你真的在写一个编译器,否则深入研究它并没有多大用处。

如果您熟悉汇编程序,您可以查看编译后的代码,看看它生成了哪些指令。但是请记住,不同的编译器(即使是具有不同优化设置的相同编译器)很可能会生成不同的程序集。

【讨论】:

    猜你喜欢
    • 2021-12-06
    • 1970-01-01
    • 1970-01-01
    • 2021-03-18
    • 1970-01-01
    • 2015-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多