7

以下 c 程序的输出为:0.000000

输出背后是否有逻辑,或者答案编译器是否依赖,或者我只是得到一个垃圾值?

#include<stdio.h>

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

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

4

6 回答 6

9

来自最新的C11 草案

§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.
于 2012-07-26T16:13:31.890 回答
7

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

另一方面,查看行为未由语言标准定义的代码的行为可能是有益的(只要您注意不要过度概括该行为)。

printf"%f"格式需要一个类型的参数double,并以十进制形式打印它,没有指数。非常小的值将打印为0.000000.

当你这样做时:

int x=10;
printf("%f", x);

我们可以根据您所在平台的一些假设来解释可见行为:

  • int是 4 个字节
  • double是 8 个字节
  • intdouble参数被传递给printf使用相同的机制,可能在堆栈上

因此调用将(似是而非)将int10作为 4 字节数量压入堆栈,并将从堆栈中printf抓取 8 个字节的数据并将其视为 a 的表示double。4 个字节将表示10(以十六进制表示,0x0000000a);其他 4 个字节将是垃圾,很可能为零。垃圾可以是 8 字节数量的高位或低位 4 个字节。(或其他任何东西;请记住,行为是未定义的。)

这是我刚刚放在一起的演示程序。它不是滥用printf,而是使用 . 将对象的表示复制intdouble对象中memcpy()

#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;
}

我的 x86 系统上的输出是:

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

如您所见, 的值double非常小(有关详细信息,您可以查阅有关 IEEE 浮点格式的参考资料),足够接近零,将其"%f"打印为0.000000.

让我再次强调行为是未定义的,这意味着语言标准对程序的行为“没有要求”。字节顺序、浮点表示和参数传递约定的变化可以极大地改变结果。甚至编译器优化也会影响它;允许编译器假设程序的行为是明确定义的,并基于该假设执行转换。

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

于 2012-07-26T16:43:55.287 回答
1

因为二进制中的整数 10 看起来像这样:

00000000 00000000 00000000 00001010

printf 所做的只是采用内存中的表示并尝试将其表示为 IEEE 754 浮点数。

浮点数由三个部分组成(从 MSB 到 LSB):

符号:1 位
指数:8 位
尾数:23 位

由于整数 10 在尾数位中只是 1010,因此它是一个非常小的数字,远低于 printf 浮点格式的默认精度。

于 2012-07-26T16:20:44.613 回答
1

结果未定义。

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

完整的克里斯优秀答案

你的 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 (ie pf) 的指针指向托管整数值 (ie i) 的地址,因此您将得到未定义的,并且很可能是垃圾。

我想看看那段记忆里到底是什么!

如果你真的想看看垃圾背后可能是什么(在某些平台上调试时),请尝试以下代码,我们将使用联合来模拟一块内存,我们将在其中写入 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 ;

fandi字段用于设置值,字段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。

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

但我想将一个 int 打印为双精度!

严重地?

Helios提出了一个解决方案

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 将其正确解释为双精度。

于 2012-07-26T17:18:19.940 回答
0

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

于 2012-07-26T16:13:51.813 回答
0

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

int i = 3;
printf("%f",(float)(i));
于 2012-07-26T16:30:55.637 回答