【问题标题】:using printf to print out floating values使用 printf 打印出浮动值
【发布时间】:2011-09-03 18:35:50
【问题描述】:
#include<stdio.h>
#include<stdlib.h>

int main(void)
{
  int x, *ptr_x;
  float f , *ptr_f;

  ptr_f = &f;
  ptr_x = &x;
  *ptr_x = 5;
  *ptr_f = 1.5; //printf("%d %f\n", f,x);

  printf ("\n\nxd = %d \t xf = %f \n ff = %f \t fd = %d", x,x,f,f);
  return 0;
}

ff = %f 的输出不是预期的。

xd = 5 xf = 0.000000
ff = 0.000000 fd = 1073217536

这段代码的重点是显示如果使用 %d 打印浮点值并且使用 %f 打印 int 值会发生什么。

为什么即使我使用 %f 也无法正确打印浮点值?

【问题讨论】:

    标签: c pointers printf


    【解决方案1】:

    printf() 不是类型安全的。

    您传递给printf() 的参数将根据对编译器的承诺进行处理。

    此外,floats 在通过可变参数传递时会提升为 doubles。

    因此,当您第一次向编译器 %f 承诺时(对于 xf),编译器会从参数中吞噬整个 double(通常为 8 个字节),并在此过程中吞噬您的浮点数。然后第二个 %f 直接切入第二个 double 的零尾数。

    这是你的论点的图片:

    +-0-1-2-3-+-0-1-2-3-+-0-1-2-3-4-5-6-7-+-0-1-2-3-4-5-6-7-+
    |    x    |    x    |        f        |        f        |
    +---------+---------+-----------------+-----------------+
    
    %d--------|%f----------------|%f---------------|%d------|
    

    但是f看起来是这样的(已经升级为double):

    f = 3FF8000000000000
    

    让我们用值再次绘制它,并推测您的机器字节序:

    | 05000000 | 05000000 | 00000000 0000F83F | 00000000 0000F83F |
    | %d, OK   | %f, denormal...    | %f, denormal...   | %d, OK  |
    

    请注意,1073217536 是 0x3FF80000。

    【讨论】:

    • 这是高度实现定义的。对于某些编译器,它可能是正确的。对于其他人 - 不是。例如,一些现代 64 位编译器在 8 字节字段中传递所有可变参数,而不管它们的实际大小(而不是像你的图片那样紧密地打包它们)。如果 OP 使用这样的编译器,问题可能会一直隐藏。
    • 是的,关于这个答案的所有内容都完全依赖于机器,并且充斥着猜测和虚构。关于它唯一可移植的部分是最初的警告。
    • 哇,非常感谢您花时间创建如此有用的图表,太棒了!但是为什么以及如何提升花车?如果我将浮点数传递给用户定义的函数,是否会发生类似的事情?可变参数是什么意思?
    • 好的..我用谷歌搜索了可变参数函数,仍然..我不明白为什么需要促销。
    • @tera:提升只是可变参数规则的一部分。我没有头顶的参考,但如果你愿意,我可以查一下。字符和短裤被提升为整数,并浮动为双精度数。请注意您只有格式说明符%f%Lf,但float 没有。不过,C 和 C++ 之间可能存在细微差别,我现在不确定,我必须检查一下。
    【解决方案2】:

    一旦您将至少一个 无效格式说明符传递给printf(例如尝试打印带有%dfloat 值或带有intint 值)您的整个程序被搞砸了,无法修复。这种破坏性行为的后果可以在程序的任何地方看到。在您的情况下,尝试使用无效的格式说明符打印某些内容会导致即使是有效的格式说明符也停止工作。

    正式地说,您编写了一个展示未定义行为的程序。它的行为绝对无法预测。你自己说的

    这段代码的重点是显示如果浮动 value 用 %d 打印,如果 int 值打印 %f。

    你观察到的破坏行为正是证明了这一点!当你尝试做类似的事情时,一个奇怪且不可预测的程序正是发生的事情。

    【讨论】:

      【解决方案3】:

      试试这个:

      printf("size of int = %d, size of float = %d, size of double = %d\n",
          sizeof(int), sizeof(float), sizeof(double));
      

      当您调用printf() 时,系统会将参数压入堆栈。所以堆栈看起来像这样:

      pointer to format string [probably 4 bytes]
      x [probably 4 bytes]
      x [probably 4 bytes]
      f [probably 6 or 8 bytes]
      f [probably 6 or 8 bytes]
      

      然后printf() 在解析格式字符串时从堆栈中弹出字节。当它看到%d 时,它会弹出足够的字节用于int,而当它看到%f 时,它会弹出足够的字节用于float。 (实际上,浮点数在作为函数参数传递时被提升为双精度数,但重要的想法是它们需要比整数更多的字节。)因此,如果您对参数“撒谎”,它将弹出错误的字节数并盲目根据您的指示进行转换。

      所以它会首先弹出xd 的正确字节数,因为你已经正确地告诉它 x 是一个 int。

      但随后它将弹出足够的字节用于浮点数,这将消耗堆栈中的第二个x 和第一个f 的一部分,并将它们解释为xf 的浮点数。

      然后它将弹出足够的字节用于另一个浮点数,这将消耗第一个f 的剩余部分和第二个f 的一部分,并将它们解释为ff 的浮点数。

      最后,它将为 int 弹出足够的字节,这将消耗第二个 f 的剩余部分,并将它们解释为 fd 的 int。

      希望对您有所帮助。

      【讨论】:

      • "...系统将参数推入堆栈"。如果系统没有堆栈?在这种情况下会发生什么?
      • 非常感谢,这很有意义。但是你是什么意思浮动被提升为双打?这背后的逻辑是什么?在我看来,这正是我面临这个问题的原因,即如果浮点数保持为 4 个字节而不是提升为 8 个字节,一切都会好起来的。在我的机器上,int 和 float 是 4 个字节,double 是 8 个字节。
      • @tera:这就是没有原型的函数以及可变参数函数在 C 中的工作方式。所有较小的整数值都提升为 int,而所有较小的浮点类型都提升为 double。换句话说,不可能将charshortfloat 值直接传递给可变参数函数。每次您这样做时,都会首先提升价值。
      • @AndreyT:每个 C 程序至少有一个栈和堆;它们只不过是一块或多块预先分配的内存。在main() 例程之前运行的启动代码执行分配内存、初始化堆栈指针和全局/静态变量以及任何其他必要的内务处理的“魔术”。一个好的调试器可以让你单步调试这段代码并观察初始化的发生。您通常可以在编译器库或构建工具中找到启动代码的源代码。一个例子在这里:bravegnu.org/gnu-eprog/c-startup.html
      • @Adam Liss:不是这样。每个 C 程序都有 freestore(或“堆”),但根本无法保证物理“堆栈”的存在。语言规范中没有“堆栈”。而且物理堆栈并不是真正需要的,尤其是在参数传递方面。例如,在某些较旧的平台上,存在堆栈,但堆栈太小且太有价值,无法“浪费”在本地存储上,甚至无法传递参数。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-04
      • 2017-02-14
      • 2017-03-19
      • 1970-01-01
      • 1970-01-01
      • 2012-03-18
      • 2016-09-02
      相关资源
      最近更新 更多