13

为什么这个程序给出了意想不到的数字(例如:2040866504-786655336)?

#include <stdio.h> 
int main()
{
     int test = 0;
     float fvalue = 3.111f;
     printf("%d", test? fvalue : 0);

     return 0;
}

为什么它打印意外的数字而不是0?它应该进行隐式类型转换吗?这个程序是为了学习目的,没什么大不了的。

4

4 回答 4

18

最有可能的是,您的平台在浮点寄存器中传递浮点值,在不同的寄存器(或堆栈上)中传递整数值。你告诉printf寻找一个整数,所以它在寄存器中寻找整数被传入(或堆栈上)。但是你传递了它 a ,所以零被放置在从未看过float的浮点寄存器中。printf

三元运算符遵循语言规则来决定其结果的类型。它有时不能是整数,有时不能是浮点数。这些可能是不同的大小,存储在不同的位置等等,这将导致无法生成处理这两种可能结果类型的健全代码。

这是一个猜测。也许正在发生一些完全不同的事情。未定义的行为未定义是有原因的。如果没有大量的平台和编译器细节的经验和知识,这些事情可能无法预测并且很难理解。永远不要让某人说服你 UB 是好的或安全的,因为它似乎在他们的系统上工作。

于 2013-09-28T05:17:57.543 回答
11

因为您正在使用%d打印float值。使用%f. 用于%d打印float值会调用未定义的行为


编辑:关于OP的评论;

为什么它打印随机数而不是 0?

当你编译这段代码时,编译器应该给你一个警告:

[Warning] format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat]

这个警告是不言自明的,这行代码正在调用未定义的行为。这是因为,转换规范%d指定printfint值从二进制转换为十进制数字字符串,而%ffloat值执行相同操作。在传递fvalue编译器时,它知道它是floattype ,但另一方面它看到它printf需要一个 type 的参数int。在这种情况下,有时它会达到您的预期,有时它会达到我的预期。有时它会做没有人预料到的事情(David Schwartz的好评论)。
请参阅测试用例12。它与%f.

它应该进行隐式类型转换吗?

不。

于 2013-09-28T05:06:12.653 回答
8

尽管现有的赞成答案是正确的,但我认为它们过于技术性并且忽略了初学者程序员可能拥有的逻辑:

让我们看一下引起一些人困惑的陈述:

printf("%d", test? fvalue : 0);
        ^    ^     ^        ^
        |    |     |        |
        |    |     |        - the value we expect, an integral constant, hooray!
        |    |     - a float value, this won't be printed as the test doesn't evaluate to true 
        |    - an integral value of 0, will evaluate to false
        - We will print an integer!

编译器看到的有点不同。他同意test意义的价值false。他同意fvaluebeeing afloat0一个整数。但是,他了解到,三元运算符的不同可能结果必须是同一类型!intfloat不是。在这种情况下,“ floatwins”0变为0.0f!

现在printf不是类型安全的。这意味着您可以错误地说“打印我一个整数”并传递一个浮点数而编译器不会注意到。正是那件事发生了。无论 的值test是什么,编译器都会推断出结果的类型是float。因此,您的代码相当于:

float x = 0.0f;
printf("%d", x);

此时,您会遇到未定义的行为。根本float不是.integral%d

观察到的行为取决于您使用的编译器和机器。您可能会看到跳舞的大象,尽管大多数终端不支持那个 afaik。

于 2013-09-28T11:22:17.307 回答
1

当我们有表达式E1 ? E2 : E3时,涉及到四种类型。Expressions E1E2并且E3每个都有一个类型(并且 和 的类型E2可以E3不同)。此外,整个表达式E1 ? E2 : E3有一个类型。

如果E2E3具有相同的类型,那么这很容易:整个表达式具有该类型。我们可以用这样的元符号来表达这一点:

(T1 ? T2 : T2) -> T2 
"The type of a ternary expression whose alterantives are both of the same type T2
 is just T2."

如果它们不具有相同的类型,事情就会变得有些有趣,并且情况与算术运算非常相似E2E3一起参与。例如,如果将 and 相加intfloatint操作数将转换为float。这就是您的程序中正在发生的事情。类型情况是:

(int ? float : int) -> float

测试失败,因此将intvalue0转换为floatvalue 0.0

此值与 的转换说明符float不兼容,后者需要.%dprintfint

更准确地说,该float值又经历了一次。当 afloat作为尾随参数之一传递给可变参数函数时,它会转换为double.

所以实际上double值 0.0 被传递到printf它期望的地方int

在任何情况下,它都是未定义的行为:它是 C 语言的 ISO 标准定义没有提供意义的不可移植代码。

从这里开始,我们可以应用特定于平台的推理,为什么我们不只是看到0. 假设int是 32 位、4 字节类型,并且double是常见的 64 位、8 字节、IEE754 表示,并且全位为零用于0.0. 那么,为什么这个全位零的 32 位部分不被printf视为int0呢?

很可能,64 位double参数值在放入堆栈时强制 8 字节对齐,可能会将堆栈指针移动 4 个字节。然后printf从这四个字节中提取垃圾,而不是从double值中提取零位。

于 2013-11-16T06:41:33.857 回答