【问题标题】:Printing int as float in C在C中将int打印为浮点数
【发布时间】:2013-05-12 12:55:11
【问题描述】:

我正在为 Little Endian 使用 Visual Studio TC 编译器。 以下是一段代码:

void main()
{    
    float c = 1.0;
    int a = 0x3F800000;
    int *ptr = (int *)&c;
    printf("\n0x%X\n", *ptr);
    printf("\na = %f", a);
    printf("\nc = %f", c);
    return;    
}

输出是:

0x3F800000
a = 0.000000
c = 1.000000

浮点值 1.0 是 0x3F800000 并在内存中存储为 00 00 80 3F 用于 Little Endian。 将相同的值分配给 int a。 printf 如何为 int 打印 0.000000 而为 float c 打印 1.000000?我已经看到它在 printf 中使用 %f 打印时将所有整数值打印为 0.000000。

另外,既然printf是可变参数函数,它怎么知道寄存器中传递的值是int还是float?

【问题讨论】:

  • 它是因为未定义的行为。
  • 给出%f的格式说明符并给出一个int类型的参数。
  • printf() 不会猜测作为参数给出的变量类型。它只信任用户指定的标志。
  • 任何float 参数在传递给函数时都会提升为double。要确切了解发生了什么,请让编译器生成汇编输出,并查看为每个 printf() 语句生成的代码的差异。
  • 第 7.21.6/9 节说 “如果转换规范无效,则行为未定义。如果任何参数不是相应转换规范的正确类型,则行为未定义。 "。这适用于 fprintf,但同样适用于 printf。

标签: c printf


【解决方案1】:

转换变量

printf("\na = %f", (float)a);
printf("\nc = %f", (float)c);

【讨论】:

  • 这没有回答问题,这是为什么它的行为方式,而不是如何改变行为。
  • -1 加上它甚至不起作用,因为它将 a 的值转换为浮点数。为此,他可以*((float*)&a)
【解决方案2】:

我已经在 gcc 中编译了你的代码,生成的代码如下:

movl    $0x3f800000, %eax
movl    %eax, -4(%ebp)
movl    $1065353216, -8(%ebp)
leal    -4(%ebp), %eax
movl    %eax, -12(%ebp)
movl    -12(%ebp), %eax
movl    (%eax), %eax
movl    %eax, 4(%esp)
movl    $LC1, (%esp)
call    _printf
movl    -8(%ebp), %eax
movl    %eax, 4(%esp)
movl    $LC2, (%esp)
call    _printf
flds    -4(%ebp)
fstpl   4(%esp)
movl    $LC3, (%esp)
call    _printf

这可以给你一个提示,浮点参数不是从常规堆栈中获取的,而是从浮点堆栈中获取的……我希望会有一些随机的而不是 0……

【讨论】:

  • 您知道浮点寄存器 ST0、ST1 等(它们像堆栈一样工作。您从内存中压入(加载)一个数字,所有 ST(x + 1) = STx 和 ST0 都设置为你的价值)...
  • 很抱歉删除我的评论,让你的评论看起来“挂起”。您的回答看起来好像有一个“常规”堆栈和一个“浮动堆栈”。这不仅是错误的,而且是特定于 x86 平台的,恕我直言,对于这个问题没有帮助。 x86 处理浮点寄存器 like 堆栈这一事实不会影响观察到的行为。
  • 我认为这个问题的答案是特定于平台的。当然,行为是未定义的。特别是在 x86 平台上,这种行为是由以下事实引起的:浮点数存储在浮点寄存器中,而其他所有内容都在堆栈中。
  • 我们找错了树,Medinoc 做对了:%f 从堆栈中弹出(64 位)double 并打印出来。在低端机器上,(32 位)0x3F800000 转换为(64 位)双精度不足以显示默认(6 位)精度。尝试提高精度,为a 使用 64 位类型,或者(这就是说服的原因)调用printf( "%f\n", 0, a )a 的值推到对@987654328 重要的位置@。兄弟失败,梅迪诺克+1。 (当然,仍然是 UB。)
【解决方案3】:

正如-Wall 所说:warning: format ‘%f’ expects type ‘double’, but argument 2 has type ‘int’。这是未定义的行为,也更详细地解释了here

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

所以你在这里看到的是编译器构建器决定发生的事情,可以是任何东西。

【讨论】:

  • 编译器可以特别对待 printf 吗?编译器知道如何处理 printf 吗?
  • 不,但它们定义了在无效类型转换的情况下会发生什么。
  • 但是警告谈到了根据“格式说明符”检查它的参数无效性,对吗?
  • 啊,是的,你是对的,我也明白了原因L1。 +1
【解决方案4】:
int a= 0x3F800000;
printf("\na = %f", *(float*)&a);//1.0

【讨论】:

    【解决方案5】:

    我的超能力告诉我 Adam Liss 的评论是正确的答案:float 参数被提升为 double,所以 printf() 函数预计会发生这种情况:它预计堆栈上有一个 64 位的值,但是获取 32 位加上碰巧为零的垃圾数据。

    如果你提高显示精度,显示应该类似于a = 0.00000000001

    这也意味着这应该可以工作:

    void main()
    {    
        double c = 1.0;
        long long a = 0x3FF0000000000000;
        long long *ptr = (long long *)&c;
        printf("\n0x%llX\n", *ptr);
        printf("\na = %f", a);
        printf("\nc = %f", c);
        return;    
    }
    

    【讨论】:

    • Medinoc 非常感谢。这解释了一切。
    • +1。我确实花了半个小时试图证明你错了。反汇编显示double c 立即在 FPU 寄存器中创建(而不是float c),所以我怀疑这是出于错误的原因“修复”您的结果。但即使完全删除double,仍然得到a 的观察输出。然后我怀疑 GCC 对 64 位整数的特殊处理......但是当一个普通的 printf( "%f\n", 0x0, 0x3ff0000 )1.0 没有任何 doublelong long 涉及时,我不得不承认失败。看 FPU 寄存器简直是天方夜谭。
    【解决方案6】:

    在任何 C 实现中,都有关于如何将参数传递给函数的规则。这些规则可能会说某些类型的参数在某些寄存器中传递(例如,通用寄存器中的整数类型和单独的浮点寄存器中的浮点类型),大参数(例如具有许多元素的结构)在堆栈或指向结构副本的指针,等等。

    在被调用函数中,函数在规则指定的位置查找它所期望的参数。当您将参数中的整数传递给 printf 但将其传递给格式字符串中的 %f 时,您将整数放在某处,但告诉 printf 查找浮点数(已提升为双精度数)。如果您的 C 实现规则指定整数参数与双精度参数在同一位置传递,那么printf 将找到您的整数位,但会将它们解释为双精度。另一方面,如果您的 C 实现的规则为参数指定了不同的位置,那么您的整数位不在 printf 查找双精度的位置。所以printf 会找到一些与你的整数无关的其他位。

    此外,许多 C 实现具有 32 位 int 类型和 64 位 double 类型。 %f 说明符用于打印双精度,而不是浮点,并且您传递的浮点值在调用函数之前转换为双精度。因此,即使printf 找到整数的位,那里也只有 32 位,但 printf 使用 64。所以打印的 double 由您传递的 32 位和其他 32 位组成,并且这不是您要打印的值。

    这就是为什么您使用的格式说明符必须与您传递的参数匹配。

    【讨论】:

      【解决方案7】:

      我遇到了类似的问题,最后我开发了一种方法来解决它,不确定这是否是你想要的。关键是:你应该传递一个浮点数而不是整数。

      #include <stdio.h>
      void printIntAsFloat();
      
      int main(void){
          printIntAsFloat();
      }
      
      void printIntAsFloat(){
          float c = 1.0;
          int a = 0x3F800000;
          int *ptr = (int *)&c;
          float *fp = (float*)((void*)&a); /*this is the key point*/
          printf("\n0x%X\n", *ptr);
          printf("\na = %f", a);
          printf("\nc = %f", c);
          printf("\nfp = %f", *fp);
          return; 
      } 
      

      输出是这样的:

      0x3F800000
      
      a = 0.000000
      c = 1.000000
      fp = 1.000000
      

      操作系统:Fedora21 64 位 GCC 版本:gcc 版本 4.9.2 20141101 (Red Hat 4.9.2-1) (GCC)

      【讨论】:

      • 这仍然有未定义的行为,所有相同的原始问题仍然存在,如之前的答案和 cmets 中讨论的那样。您所做的是偶然发现了一种行星对齐组合,该组合现在似乎可以工作,在一个具有一个编译器版本和设置组合的系统上。
      • 我对C不是很熟悉,能不能稍微解释一下哪行代码包含未定义的行为?
      • 第 13、14、16 行;第 11 行和第 12 行可能会。我不想重复已经说过的话,所以请阅读其他 cmets。
      • 你是对的。我只是意识到我所做的与其他人之前讨论过的完全一样。
      猜你喜欢
      • 1970-01-01
      • 2020-11-11
      • 1970-01-01
      • 2014-09-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多