【问题标题】:Why do the first and the third printf work so differently? [duplicate]为什么第一个和第三个 printf 的工作方式如此不同? [复制]
【发布时间】:2013-07-07 17:20:32
【问题描述】:
printf("line 5: %f\n",98);  //output is 0.000000
printf("line 6: %f\n",98.98); //output is 98.980000  
printf("line 5: %f\n",98);//though same as first printf statement but output is 98.979980

虽然第一个和最后一个 printf 语句完全相同,但它们的输出不同。为什么?

因为 int 在期望浮点数时被传递给 printf,这就是为什么它工作起来很奇怪。但我的观点是,为什么在最后一个打印语句中,不是打印一些垃圾值或 0,而是使用第二个 printf 语句的值,这就是打印的内容。

【问题讨论】:

  • @hbrock,编译器无关紧要。 OP 的代码会导致未定义的行为。这个问题重复了数百次。
  • 因为 int 在期望浮点数时被传递给 printf,这就是为什么它工作起来很奇怪。但我的观点是,为什么在最后一个打印语句中,不是打印一些垃圾值或 0,而是使用第二个 printf 语句的值,这就是打印的内容。

标签: c printf


【解决方案1】:

正如其他人已经说过的那样,将 int 传递给 printf 当它期望 double 时会导致未定义的行为,并且任何事情都可能发生。您可能会对为什么程序在第三行打印98.979980 而不是随机数的原因感兴趣。

参数被传递给堆栈上的printf。当第 2 行将 98.98 传递到 printf 时,它被压入堆栈,最不重要的部分在前。

然后printf 返回,在第三行再次调用它,现在98 被压入堆栈。在您的架构上,int 类型似乎是 32 位; double 类型的一半大小,所以这只会覆盖之前在堆栈上的 98.98 的下半部分。 98.98 的上半部分仍在堆栈中。

现在对printf 的第三次调用从堆栈中读取double。它读取的最重要的一半来自之前堆栈中的98.98,而不太重要的一半来自98的二进制表示;这就是结果如此接近98.98 的原因。由于 98 是一个很小的数字,它的最高有效位将为 0,将98.98 的最低有效一半设置为大部分为零会给您一个较小的数字。

如果第 3 行使用了一个将更多位设置为 1 的数字,您将得到大于 98.98 的结果。例如,-1 的二进制表示将其所有位设置为 1,您会得到:

printf("line 2: %f\n", 98.98); # 98.98
printf("line 3: %f\n", -1);    # 98.980042

如果编译器使用 64 位整数,或者先传递最重要部分的 doubles,或者使用寄存器而不是堆栈来传递参数,你会得到非常不同的结果。

【讨论】:

    【解决方案2】:

    因为您的程序调用了未定义的行为。 98 的类型为 int,但 %f 需要 float(或 double,由于默认提升规则)。

    因此,由于printf() 在转换说明符的类型与实际类型不匹配时具有 UB,因此没有任何合理的解释。

    【讨论】:

      【解决方案3】:

      %f 需要 double,但您传递的是 int 值。这是未定义的行为。

      正确的应该是:

      printf("line 5: %f\n",98.0); 
      printf("line 6: %f\n",98.98); 
      printf("line 5: %f\n",98.0);
      

      【讨论】:

        【解决方案4】:

        这是因为%f 需要一个双参数。提供int 是未定义的行为。

        ISO/IEC 9899:1999, §7.19.6.1, 9:

        如果任何参数不是相应转换规范的正确类型,则行为未定义。

        Undefined behavior 指行为不可预测的计算机代码。

        如果启用警告,至少使用 gcc 你会收到相应的警告:

        警告:格式“%f”需要类型“double”,但参数 2 的类型为“int”

        【讨论】:

          【解决方案5】:

          如果我们查看编译器生成的代码,我们会看到以下内容:

          00401B5E|>MOV DWORD PTR SS:[ESP+0x4],0x62                          ; |||
          00401B66|>MOV DWORD PTR SS:[ESP],arma_sto.00404024                 ; |||ASCII "line 5: %f\n"
          00401B6D|>CALL <JMP.&msvcrt.printf>                                ; ||\printf
          00401B72|>MOV DWORD PTR SS:[ESP+0x4],0x51EB851F                    ; ||
          00401B7A|>MOV DWORD PTR SS:[ESP+0x8],0x4058BEB8                    ; ||
          00401B82|>MOV DWORD PTR SS:[ESP],arma_sto.00404030                 ; ||ASCII "line 6: %f\n"
          00401B89|>CALL <JMP.&msvcrt.printf>                                ; |\printf
          00401B8E|>MOV DWORD PTR SS:[ESP+0x4],0x62                          ; |
          00401B96|>MOV DWORD PTR SS:[ESP],arma_sto.00404024                 ; |ASCII "line 5: %f\n"
          00401B9D|>CALL <JMP.&msvcrt.printf>                                ; \printf
          

          因为您没有将这两个 98 值转换为浮点数,所以输出是随机的(基于堆栈)。 %f 的有效输入是一个浮点数,它在堆栈上占用两个条目。

          第 4 行不起作用,因为您只提供了一个堆栈条目。

          第 5 行运行良好,因为 98.98 是一个浮点数(需要两个堆栈条目)

          第 6 行输出 ~98.98,因为 00401B7A 处的 MOV 未撤消。这意味着第 6 行输出了一个有效的浮点数,因为它有两个堆栈条目,但由于前一个数字还剩下一部分,所以输出了错误的浮点数。

          解决方案,转换为浮点数

          printf("line 5: %f\n",(float)98);  //output is 98.000000
          printf("line 6: %f\n",98.98); //output is 98.980000  
          printf("line 5: %f\n",(float)98); //output is 98.000000
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-11-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-12
            • 2018-12-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多