该程序如何工作?


88
#include <stdio.h>

int main() {
    float a = 1234.5f;
    printf("%d\n", a);
    return 0;
}

它显示一个0!! 那怎么可能?这是什么原因?


我故意%dprintf声明中加入,以研究的行为printf

Answers:


239

那是因为%d期望一个,int但是您提供了一个浮动。

使用%e/ %f/ %g打印浮点数。


关于为什么打印0的原因:在将浮点数double发送到之前将其转换为printf。小尾数中双精度表示形式的数字1234.5为

00 00 00 00  00 4A 93 40

A %d消耗32位整数,因此输出零。(作为测试,printf("%d, %d\n", 1234.5f);您可以得到输出0, 1083394560。)


至于为什么将6.5.2.2/7从printf的原型float转换为,doubleint printf(const char*, ...)

函数原型声明器中的省略号引起参数类型转换在最后声明的参数之后停止。默认参数提升是对尾随参数执行的。

从6.5.2.2/6开始,

如果表示被调用函数的表达式的类型不包含原型,则对每个参数执行整数提升,并将具有该类型的参数float提升为double这些称为默认参数提升

(感谢Alok找出这个问题。)


4
+1最佳答案。它回答了“标准技术上正确”的原因和“您可能的实现方式”的原因。
克里斯·卢茨

12
我相信您是实际提供他所寻找答案的12个人中的唯一一个。
加布

11
因为printf是可变函数,并且标准说对于可变函数,a 在传递之前float转换为double
Alok Singhal'3

8
从C标准开始:“函数原型声明器中的省略号引起参数类型转换在最后声明的参数之后停止。默认的参数提升对尾随的参数执行。” 和“ ...以及类型为float的参数被提升为double。这些被称为默认参数Promotions”
Alok Singhal

2
在中使用不正确的格式说明符会printf()调用未定义的行为
Prasoon Saurav'3

45

从技术上讲是没有 printf,每个库实现了自己,因此,你的努力学习方法printf做,你在做什么,是不是要多大用处的行为。您可能正在尝试研究printf系统上的行为,如果是,则应阅读文档,并查看源代码以printf了解库是否可用。

例如,在我的Macbook上,我1606416304随程序一起输出。

话虽如此,当您将a传递float给可变参数函数时,float会作为传递double。因此,您的程序等效于声明adouble

要检查a的字节double,您可以在SO上看到最近问题的答案

让我们这样做:

#include <stdio.h>

int main(void)
{
    double a = 1234.5f;
    unsigned char *p = (unsigned char *)&a;
    size_t i;

    printf("size of double: %zu, int: %zu\n", sizeof(double), sizeof(int));
    for (i=0; i < sizeof a; ++i)
        printf("%02x ", p[i]);
    putchar('\n');
    return 0;
}

当我运行上述程序时,我得到:

size of double: 8, int: 4
00 00 00 00 00 4a 93 40 

因此,原来的前四个字节为double0,这可能就是为什么将其0作为printf调用的输出。

为了获得更有趣的结果,我们可以对程序进行一些更改:

#include <stdio.h>

int main(void)
{
    double a = 1234.5f;
    int b = 42;

    printf("%d %d\n", a, b);
    return 0;
}

当我在Macbook上运行上述程序时,我得到:

42 1606416384

在Linux机器上使用相同的程序,我得到:

0 1083394560

为什么要编程反向打印?我想念什么吗?如果b是int = 42,并且'%d'是整数格式,为什么它不是第二个打印值,因为它是printf参数中的第二个变量?这是错字吗?
Katastic Voyage's

可能是因为int参数在不同的寄存器不是通过double参数。 printfwith %d接受int参数42,第二%dint参数可能会显示垃圾,因为没有第二个参数。
阿洛克·辛哈尔

20

%d说明符告诉printf期待一个整数。因此,float的前四个(或两个,取决于平台)字节被解释为整数。如果它们恰好为零,则打印零

1234.5的二进制表示形式类似于

1.00110100101 * 2^10 (exponent is decimal ...)

使用一个float实际上表示为IEEE754双精度值的C编译器,字节将是(如果我没有记错的话)

01000000 10010011 01001010 00000000 00000000 00000000 00000000 00000000

在字节序很少的Intel(x86)系统上(即,最低有效字节排在最前面),该字节序列被反转,因此前四个字节为零。也就是说,什么printf打印出来...

有关IEEE754的浮点表示,请参见此Wikipedia文章


7

这是因为浮点数以二进制表示。转换为整数会使它保留为0。


1
您似乎是唯一了解他的要求的人。除非我当然误会我自己。
Mizipzor

我同意答案是不准确和不完整的,但没有错。
Shaihi 2010年

7

因为您调用了未定义的行为:您对它的参数类型说谎,从而违反了printf()方法的约定,所以编译器可以自由地执行自己喜欢的任何事情。它可能会使程序输出“ dksjalk是一个ninnyhead !!!” 从技术上讲,这仍然是正确的。


5

原因是那printf()是一个相当愚蠢的功能。它根本不检查类型。如果您说第一个参数是,int(这就是您所说的%d),那么它会相信您,并且只占用所需的字节int。在这种情况下,假设您的计算机使用4字节int和8字节double(将float其转换为doubleinside printf()),则前四个字节a将只是零,并将其打印出来。


3

它不会自动将float转换为整数。因为两者具有不同的存储格式。因此,如果要转换,请使用(int)类型转换。

#include <stdio.h>

int main() {
    float a = 1234.5f;
    printf("%d\n", (int)a);
    return 0;
}

2

由于您也使用C ++对其进行了标记,因此该代码将按照您的期望进行转换:

#include <iostream.h>

int main() {
    float a = 1234.5f;
    std::cout << a << " " << (int)a << "\n";
    return 0;
}

输出:

1234.5 1234


1

您只需要使用具有相关数据类型(int,float,string等)的适当格式说明符(%d,%f,%s等)。


问题不是如何解决它,而是为什么它以其工作方式起作用。
2010年


0

嘿,它必须打印一些东西,所以它打印了0。请记住,在C 0中,其他所有内容!


1
怎么了 在C语言中,没有其他任何东西。
Vlad

如果x是某物,则!x == 0 :)
chunkyguy 2010年

if(iGot ==“ everything”)打印“一切”;否则打印“无”;
尼克

0

不是整数。尝试使用%f


我想问题是,为什么不将float转换为int并显示“ 1234”?
比昂·波莱克斯(BjörnPollex)2010年

不要指望c不要让你做一些没有逻辑意义的事情。是的,许多语言都会给出您可能期望的1234,甚至c的某些实现我都不会定义此行为。C让您像父母一样上吊,让您尽其所能。
重播

因为C被设计为灵活的。
纠缠
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.