【问题标题】:How is conversion of float/double to int handled in printf?如何在 printf 中处理 float/double 到 int 的转换?
【发布时间】:2011-01-24 19:44:02
【问题描述】:

考虑这个程序

int main()
{
        float f = 11.22;
        double d = 44.55;
        int i,j;

        i = f;         //cast float to int
        j = d;         //cast double to int

        printf("i = %d, j = %d, f = %d, d = %d", i,j,f,d);
        //This prints the following:
        // i = 11, j = 44, f = -536870912, d = 1076261027

        return 0;
}

有人能解释一下为什么从 double/float 到 int 的转换在第一种情况下可以正常工作,而在 printf 中则不能正常工作吗?
该程序是在 32 位 linux 机器上的 gcc-4.1.2 上编译的。


编辑: Zach's answer 似乎合乎逻辑,即使用格式说明符来确定要从堆栈中弹出什么。但是,请考虑以下后续问题:

int main()
{

    char c = 'd';    // sizeof c is 1, however sizeof character literal
                     // 'd' is equal to sizeof(int) in ANSI C

    printf("lit = %c, lit = %d , c = %c, c = %d", 'd', 'd', c, c);
    //this prints: lit = d, lit = 100 , c = d, c = 100
    //how does printf here pop off the right number of bytes even when
    //the size represented by format specifiers doesn't actually match 
    //the size of the passed arguments(char(1 byte) & char_literal(4 bytes))    

 return 0;
}

这是如何工作的?

【问题讨论】:

  • 我也有类似的疑问。看到这个线程:stackoverflow.com/questions/2377733/how-does-this-program-work
  • char 是单个字符 - 它只是一个 8 位整数。当您对小于 int 的整数类型执行任何类型的操作时,它们将被提升为整数。这包括调用函数时。所以实际上它不是随机的机会导致你的 printf 调用工作,这种行为是定义的。实际上,在大多数 C ABI 中,您总是为堆栈上传递的每个变量分配至少一个机器字。
  • @SurajJain 是的,<stdarg> 参数传递只考虑根据默认参数提升(C11 §6.5.2.2/6,§7.16.1.1/2)提升的类型,这确实保证了charint 兼容。但是,这与用于算术的提升并不完全相同。此外,根据 ABI 来推理语言是危险的。确认没问题确实需要检查规则。
  • @Potatoswatter 我的评论是怎么被删除的?
  • @SurajJain 很奇怪。这个网站有很多版主,有时事情会随机消失。

标签: c


【解决方案1】:

因为您没有使用浮点格式说明符,请尝试:

printf("i = %d, j = %d, f = %f, d = %f", i,j,f,d);

否则,如果您想要 4 个整数,则必须在将参数传递给 printf 之前强制转换它们:

printf("i = %d, j = %d, f = %d, d = %d", i,j,(int)f,(int)d);

【讨论】:

    【解决方案2】:

    printf 函数使用格式说明符来确定要从堆栈中弹出的内容。所以当它看到%d时,它会弹出4个字节并将它们解释为int,这是错误的((float)3.0的二进制表示与(int)3不同)。

    您需要使用%f 格式说明符或将参数转换为int。如果您使用的是足够新的gcc 版本,那么打开更强的警告会捕获此类错误:

    $ gcc -Wall -Werror test.c
    cc1: warnings being treated as errors
    test.c: In function ‘main’:
    test.c:10: error: implicit declaration of function ‘printf’
    test.c:10: error: incompatible implicit declaration of built-in function ‘printf’
    test.c:10: error: format ‘%d’ expects type ‘int’, but argument 4 has type ‘double’
    test.c:10: error: format ‘%d’ expects type ‘int’, but argument 5 has type ‘double’
    

    对问题中已编辑部分的回复:

    C 的整数提升规则说,所有小于int 的类型在作为可变参数传递时都会提升为int。因此,在您的情况下,'d' 被提升为int,然后 printf 弹出int 并转换为char。对于这种行为,我能找到的最佳参考是 this blog entry

    【讨论】:

    • 我有一个后续问题,请参阅我问题的编辑部分。如果问题不清楚,请发表评论...谢谢
    • 确实,char 参数在传入参数列表的变量部分时会提升为 int(这是在示例中传递 c 时发生的情况),但是字符文字常量(如'd')已经是int 类型。 (另请注意,理论上,在某些编译器下,char 参数可以提升为 unsigned int
    • 好答案。这种行为不是 printf 特有的,而是适用于所有可变参数函数。它的工作原理是有据可查的 va_arg 手册页,包括将字符提升为 int。作为使用示例,您可以在stackoverflow.com/questions/1688942 中查看我的回答
    • 第一句是特定于实现的(现在前几个参数不太常见),一些参数是通过寄存器传递的
    【解决方案3】:

    printf 使用可变长度参数列表,这意味着您需要提供类型信息。您提供了错误的信息,因此会感到困惑。 Jack 提供了实用的解决方案。

    【讨论】:

      【解决方案4】:

      Jack's answer 解释了如何解决您的问题。我将解释为什么你会得到意想不到的结果。您的代码相当于:

      float f = 11.22;
      double d = 44.55;
      int i,j,k,l;
      
      i = (int) f;
      j = (int) d;
      k = *(int *) &f;         //cast float to int
      l = *(int *) &d;         //cast double to int
      
      printf("i = %d, j = %d, f = %d, d = %d", i,j,k,l);
      

      原因是fd作为值传递给printf,然后这些值被解释为ints。这不会更改二进制值,因此显示的数字是floatdouble 的二进制表示。从floatint 的实际转换在生成的程序集中要复杂得多。

      【讨论】:

      • 可能会帮助他理解他的输出,但代码不等价。您不能为错误解释 printf 或任何可变参数函数编写等效代码,同时仍在调用可变参数函数。
      • 我的代码做了(对我来说,也可能对他来说)他的代码正在做的事情(对他来说)。两者都是未定义的行为,因此两者都不能保证做任何特别的事情。
      【解决方案5】:

      没有“在printf 中转换为int”这样的东西。 printf 不做也不能做任何铸造。不一致的格式说明符会导致未定义的行为。

      实际上printf 只是接收原始数据并重新解释它作为格式说明符所隐含的类型。如果您将double 值传递给它并指定int 格式说明符(如%d),printf 将采用double 值并盲目地将其重新解释为int。结果将是完全不可预测的(这就是为什么这样做会在 C 中正式导致未定义的行为)。

      【讨论】:

      • 当然可以投射——为什么不呢?所有的信息都在那里。编译器比我们更了解参数类型(有人提到默认提升吗?),并且转换说明符是标准的一部分。它不能正确转换的部分原因是历史原因(最初的 C 编译器过于简单,无法进行此类分析),现在的更改甚至可能会破坏依赖于重新解释的代码。
      • @Peter A. Schneider:真的吗?格式字符串是运行时值的情况呢?即使“转换说明符是标准的一部分”,您如何期望编译器“转换”任何内容?更一般地说,这是 C 语言设计的一个基本原则:它的标准库不依赖于编译器的“神奇”特性(也许只有少数例外)。几乎每个库功能都可以由用户用 C 语言本身实现。只要 C 语言遵循这一基本原则,printf 就不会被任何编译器魔法“增强”。
      • 诚然,我没有想到运行时格式字符串(因为实际上:您最后一次使用它是什么时候?)。但是标准可以区分这些情况——看看他们对sizeof做了什么。至于演员表:很明显,编译器通常知道如何转换类型,所以我在这里无法检测到任何额外的魔法;而且我也认为演员可能无论如何都可以用C来实现。就像一个大的 if/else if 切换转换运算符,然后进行适当的强制转换。突破性的变化宁愿是省略号原型的语义变化。
      • 作为实现的建议,我设想在最后声明的参数之后的参数类型已知/可以在编译时推断的情况下(如在静态格式字符串 printf 中),编译器使用给定类型的给定数量的参数创建一个未命名的函数原型,并像对待任何其他普通函数一样对待这些参数;例如当参数与推断的参数类型不兼容时,它拒绝编译或链接。这可能类似于现代 C++ 中的某些类型推断实例,即存在机制。
      【解决方案6】:

      您的后续代码有效的原因是字符常量在被压入堆栈之前被提升为 int。所以 printf 为 %c 和 %d 弹出 4 个字节。事实上,字符常量是 int 类型,而不是 char 类型。 C 那样奇怪。

      【讨论】:

        【解决方案7】:

        值得注意的是printf,作为一个具有可变长度参数列表的函数,从不接收浮点数;浮点参数是“老派”提升为双精度。

        最近的标准草案首先引入了“老派”默认促销(n1570,6.5.2.2/6):

        如果表示被调用函数的表达式有一个类型 不包括原型,整数提升在 每个参数和具有浮点类型的参数都被提升为 双倍的。这些被称为默认参数提升。

        然后讨论变量参数列表(6.5.2.2/7):

        函数原型声明器中的省略符号导致参数 在最后一个声明的参数之后停止类型转换。 默认 参数提升是在尾随参数上执行的。

        printf 的后果是不可能“打印”真正的浮点数。浮点表达式总是提升为双精度,对于 IEEE 754 实现来说是一个 8 字节的值。此促销发生在呼叫方;当 printf 开始执行时,栈上已经有一个 8 字节的参数了。

        如果我们将 11.22 分配给一个 double 并检查其内容,使用我的 x86_64-pc-cygwin gcc 我会看到字节序列 000000e0a3702640。

        这解释了printf 打印的 int 值:此目标上的 Int 仍然有 4 个字节,因此仅计算前四个字节 000000e0,并且再次以小端序计算,即 0xe0000000。这是十进制的 -536870912。

        如果我们反转所有 8 个字节,因为英特尔处理器也以小端存储双精度,我们得到 402670a3e0000000。我们可以检查这个字节序列在IEEE格式on this web site中所代表的值;它接近 1.122E1,即 11.22,即预期结果。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多