使用%f打印整数变量

时间:2012-07-26 16:09:13

标签: c

以下c程序的输出是: 0.000000 输出后面是逻辑还是编译器依赖于答案,或者我只是得到垃圾值?

#include<stdio.h>

int main()
{
    int x=10;
    printf("%f", x);
    return 0;
}

PS: - 我知道尝试使用%f打印整数值是愚蠢的。我只是从理论的角度来问这个问题。

6 个答案:

答案 0 :(得分:7)

来自最新的C11 draft

<强>§7.16.1.1/ 2

...if type is not compatible with the type of the actual next argument 
(as promoted according to the default argument promotions), the behavior 
is undefined, except for the following cases:

— one type is a signed integer type, the other type is the corresponding 
unsigned integer type, and the value is representable in both types;
— one type is pointer to void and the other is a pointer to a character type.

答案 1 :(得分:4)

最重要的是要记住,正如克里斯指出的那样,行为是未定义的。如果这是一个真正的程序,唯一明智的做法就是修复代码。

另一方面,查看行为未被语言标准定义的代码的行为可能是有益的(只要您小心不要过多地概括行为)。

printf的{​​{1}}格式需要"%f"类型的参数,并以十进制形式打印,不带指数。非常小的值将打印为double

执行此操作时:

0.000000

我们可以解释一下有关您所使用的平台的一些假设的可见行为:

  • int x=10; printf("%f", x); 是4个字节
  • int是8个字节
  • doubleint参数使用相同的机制传递给double,可能在堆栈上

因此,调用将(合理地)将printfint推送到堆栈上作为4字节数量,10将从堆栈中获取8字节数据并处理它作为printf的表示。 4个字节将是double的表示形式(十六进制,10);其他4个字节将是垃圾,很可能为零。垃圾可以是8字节数量的高阶或低阶4字节。 (或其他任何事情;记住行为未定义。)

这是我刚刚组合在一起的演示程序。它不是滥用0x0000000a,而是使用printfint对象的表示复制到double对象中。

memcpy()

x86系统的输出是:

#include <stdio.h>
#include <string.h>

void print_hex(char *name, void *addr, size_t size) {
    unsigned char *buf = addr;
    printf("%s = ", name);
    for (int i = 0; i < size; i ++) {
        printf("%02x", buf[i]);
    }
    putchar('\n');
}

int main(void) {
    int i = 10;
    double x = 0.0;
    print_hex("i (set to 10)", &i, sizeof i);
    print_hex("x (set to 0.0)", &x, sizeof x);

    memcpy(&x, &i, sizeof (int));
    print_hex("x (copied from i)", &x, sizeof x);
    printf("x (%%f format) = %f\n", x);
    printf("x (%%g format) = %g\n", x);

    return 0;
}

正如您所看到的,i (set to 10) = 0a000000 x (set to 0.0) = 0000000000000000 x (copied from i) = 0a00000000000000 x (%f format) = 0.000000 x (%g format) = 4.94066e-323 的值非常小(您可以参考有关详细信息的IEEE浮点格式的参考),接近零,double打印它为"%f"

让我再次强调行为是未定义的,这意味着语言标准“对程序的行为没有任何要求”。字节顺序,浮点表示和参数传递约定的变化可以显着改变结果。即使编译器优化也会影响它;允许编译器假定程序的行为定义良好,并根据该假设执行转换。

所以随意忽略我在这里写的所有内容(除了第一段和最后一段)。

答案 2 :(得分:1)

因为二进制的整数10如下所示:

00000000 00000000 00000000 00001010

所有printf都采用内存中表示并尝试将其表示为IEEE 754浮点数。

浮点数有三个部分(从MSB到LSB):

标志:1位
指数:8位
尾数:23位

由于整数10在尾数位中仅为1010,因此它的数量非常小,远小于printf浮点格式的默认精度。

答案 3 :(得分:1)

结果未定义。

  

我只是从理论的角度来问这个问题。

完整chrisexcellent answer

你的printf中发生的事情是未定义的,但它可能与下面的代码非常相似(这取决于varargs的实际实现,IIRC)。

免责声明:以下是对“平台上未定义的行为案例中可能发生的事情”的解释,而不是在所有平台上始终发生的真实/有效描述

定义“未定义”?

想象一下以下代码:

int main()
{
    int i       = 10 ;
    void * pi   = &i ;
    double * pf = (double *) pi ; /* oranges are apples ! */
    double f    = *pf ;

    /* what is the value inside f ? */

    return 0;
}

这里,当你指向double(即pf)的指针指向一个托管整数值的地址(即i)时,你得到的是未定义的,而且很可能是垃圾。

我想看看那个记忆里面是什么!

如果你真的想看看那些垃圾可能背后的东西(在某些平台上进行调试时),请尝试以下代码,我们将使用union来模拟一块内存,我们将写入double或int数据:

typedef union
{
   char c[8] ; /* char is expected to be 1-byte wide     */
   double f ;  /* double is expected to be 8-bytes wide  */
   int i ;     /* int is expected to be 4-byte wide      */
} MyUnion ;

fi字段用于设置值,c字段用于逐字节查看(或修改)内存。

void printMyUnion(MyUnion * p)
{
   printf("[%i %i %i %i %i %i %i %i]\n"
      , p->c[0], p->c[1], p->c[2], p->c[3], p->c[4], p->c[5], p->c[6], p->c[7]) ;
}

上面的函数将逐字节打印内存布局。

下面的函数将打印不同类型值的内存布局:

int main()
{
   /* this will zero all the fields in the union */
   memset(myUnion.c, 0, 8 * sizeof(char)) ;
   printMyUnion(&myUnion) ; /* this should print only zeroes */
                            /* eg. [0 0 0 0 0 0 0 0] */

   memset(myUnion.c, 0, 8 * sizeof(char)) ;
   myUnion.i = 10 ;
   printMyUnion(&myUnion) ; /* the representation of the int 10 in the union */
                            /* eg. [10 0 0 0 0 0 0 0] */

   memset(myUnion.c, 0, 8 * sizeof(char)) ;
   myUnion.f = 10 ;
   printMyUnion(&myUnion) ; /* the representation of the double 10 in the union */
                            /* eg. [0 0 0 0 0 0 36 64] */

   memset(myUnion.c, 0, 8 * sizeof(char)) ;
   myUnion.f = 3.1415 ;
   printMyUnion(&myUnion) ; /* the representation of the double 3.1415 in the union */
                            /* eg. [111 18 -125 -64 -54 33 9 64] */

   return 0 ;
}

注意:此代码在Visual C ++ 2010上进行了测试。

这并不意味着它会在您的平台上以这种方式(或根本不是)工作,但通常情况下,您应该得到与上述相似的结果。

最后,垃圾只是你所看到的内存中的十六进制数据集,但看作某种类型。

由于大多数类型具有不同的数据内存表示形式,因此查看除原始类型之外的任何其他类型的数据都必然会产生垃圾(或不那么垃圾)结果。

你的printf表现得很好,因此,当它最初设置为int时,尝试将原始内存解释为double。

P.S。:请注意,由于int和double的字节大小不同,垃圾变得更加复杂,但它主要是我上面描述的。

但我想打印一个int作为double!

真的?

Helios建议a solution

int main()
{
   int x=10;
   printf("%f",(double)(x));
   return 0;
}

让我们看看伪代码,看看是什么输入到printf:

   /* printf("...", [[10 0 0 0]]) ; */
   printf("%i",x);

   /* printf("...", [[10 0 0 0 ?? ?? ?? ??]]) ; */
   printf("%f",x);

   /* printf("...", [[0 0 0 0 0 0 36 64]]) ; */
   printf("%f",(double)(x));

强制转换提供不同的内存布局,有效地将整数“10”数据更改为双“10.0”数据。

因此,当使用“%i”时,会出现类似[[?? ?? ?? ??]],并且对于第一个printf,接收[[10 0 0 0]]并将其正确解释为整数。

当使用“%f”时,会出现类似[[?? ?? ?? ?? ?? ?? ?? ??]],并在第二个printf上接收类似[[10 0 0 0]]的东西,缺少4个字节。所以最后4个字节将是随机数据(可能是[[10 0 0 0]]之后的字节,即类似[[10 0 0 0 ?? ?? ?? ??]]

在最后一个printf中,强制转换更改了类型,因此内存表示更改为[[0 0 0 0 0 0 36 64]],printf会将其正确解释为double。

答案 4 :(得分:0)

基本上它是垃圾。小整数看起来像不应该存在的非标准化浮点数。

答案 5 :(得分:0)

您可以像这样强制转换int变量:

int i = 3;
printf("%f",(float)(i));