当printf中使用%d时,float变量会发生什么?

时间:2011-09-20 04:19:07

标签: c floating-point printf

我正在尝试通过阅读C Programming Language, 2nd Edition 来学习C.我有一些编程经验,但没有C。

我目前在第1章。我有以下代码:


  float f;
  for (f = 0.0; f <= 3; f += 1.1)
      printf("A: %3f B: %6.2f\n", f, f + 0.15);

打印输出:

A: 0.000000 B:   0.15
A: 1.100000 B:   1.25
A: 2.200000 B:   2.35

看起来很好。


现在我按如下方式更改printf:

printf("A: %3d B: %6.2f\n", f, f + 0.15);

新输出

A:   0 B:   0.00
A: -1610612736 B:   0.00
A: -1610612736 B: -625777476808257557292155887552002761191109083510753486844893290688350183831589633800863219712.00

这里发生了什么?我希望浮点数转换为int,因为我使用了%d,但事实并非如此。另外,为什么价值B也出错?这里发生了什么事?

4 个答案:

答案 0 :(得分:16)

当你致电:

printf("A: %3d B: %6.2f\n", f, f + 0.15);

C会自动将float值转换为double(这是在调用带有可变参数的函数时进行的标准转换,例如int printf(const char *fmt, ...);)。为了论证,我们假设sizeof(int)是4而sizeof(double)是8(有例外,但它们很少而且很远)。

因此,该调用已将指针推入堆栈,加上f的8字节双精度,以及f + 0.15的另一个8字节双精度。在处理格式字符串时,%d告诉printf()您在格式字符串后将4字节int压入堆栈。由于这不是你所做的,你已经调用了未定义的行为;根据C标准,接下来发生的一切都没问题。

然而,最可能的实现轻快地读取4个字节并将它们打印为好像它们是int(它相信你说实话)。然后它遇到%6.2f格式;它将从堆栈中读取8字节为double。有一个外部的可能性会导致内存错误导致访问错位(这需要64位机器,要求double在8字节边界上对齐,例如SPARC),否则它将会从f读取4个字节,从f + 0.15读取4个字节,将它们放在一起以创建一些非常意外的double值 - 如示例所示。

答案 1 :(得分:8)

Printf会将您指向的内存视为您告诉它的内存。没有转换。它将表示float的内存视为int。因为两者的存储方式不同,所以你得到的本质上是一个随机数。

如果要将float输出为整数,则应首先将其转换为:

printf("A: %3d B: %6.2f\n", (int)f, f + 0.15); 

答案 2 :(得分:1)

对于大多数函数,如果您传递float但函数期望使用int,则编译器知道会自动将float转换为int。但是printf是特殊的(非常特殊)。 %d期望有一个int您的工作就是将其传递给int。在printf的情况下,编译器中没有自动机制可以为您执行转换。

尽管如此,好的编译器会检测并警告此问题。如果没有,那您​​需要一个更好的。

更长的解释是,对于大多数函数,函数原型给出所有参数的数量和类型,这是使编译器了解可能需要引入转换的机制。但是printf的原型是

extern int printf(const char *, ...);

这三个点...的字面意思是:“这里的参数个数可变,不知道有多少个或什么类型”。到printf实际运行时,它会在格式字符串中找到%d,告诉它期待已通过的int,如果您通过了转换,则为时已晚,无法进行任何转换除了int以外。

答案 3 :(得分:0)

不管浮点参数如何,都可以打印任何想要的整数值

  printf("A: %d B: %6.2f\n", f, f + 0.15);

以下是在英特尔架构上打印任意整数的方法:

  int print_it(int, int /* nameless but printed */, float f)
  {
      printf("A: %d B: %6.2f\n", f, f + 0.15);
  }
  int main()
  {
      print_it(0, 12 /* will be printed */, 0.0);
      print_it(0, 123 /* printed */, 1.1);
      print_it(0, 1234 /* printed */ , 2.2);
  }

此输出:

A: 12 B:   0.00
A: 123 B:   1.10
A: 1234 B:   2.20

说明:显然,格式字符串和参数不匹配会导致未定义的行为。不过,有时可以预见。在英特尔架构上,前几个参数由寄存器传递。浮点值在不同的寄存器中传递。

尽管与问题中的printf指令相同,但输出却不同。发生的情况是12、123、1234通过负责第二个非浮点参数的通用寄存器。由于printf仅具有一个非浮点参数,因此第二个非fp参数的寄存器不变。该寄存器保留从print_it(0, int_value, fp_value)的第二个参数获得的值。

但是原始文件会带来垃圾:

  for (f = 0.0; f <= 3; f += 1.1)
      printf("A: %3f B: %6.2f\n", f, f + 0.15);

由于printf在内部调用其他函数,因此产生了不同的垃圾。这些功能破坏了printf("... %d ...", ...)读取的通用寄存器。

显然,此行为仅在通过一组单独的寄存器传递浮点参数的系统上发生。显然,只有在编译器优化未以某种方式修改代码的情况下,这种情况才会发生,因为在发生未定义的行为时,它可以做一些野蛮的事情。