为什么这个程序给出了意想不到的数字(例如:2040866504
,-786655336
)?
#include <stdio.h>
int main()
{
int test = 0;
float fvalue = 3.111f;
printf("%d", test? fvalue : 0);
return 0;
}
为什么它打印意外的数字而不是0
?它应该进行隐式类型转换吗?这个程序是为了学习目的,没什么大不了的。
为什么这个程序给出了意想不到的数字(例如:2040866504
,-786655336
)?
#include <stdio.h>
int main()
{
int test = 0;
float fvalue = 3.111f;
printf("%d", test? fvalue : 0);
return 0;
}
为什么它打印意外的数字而不是0
?它应该进行隐式类型转换吗?这个程序是为了学习目的,没什么大不了的。
最有可能的是,您的平台在浮点寄存器中传递浮点值,在不同的寄存器(或堆栈上)中传递整数值。你告诉printf
寻找一个整数,所以它在寄存器中寻找整数被传入(或堆栈上)。但是你传递了它 a ,所以零被放置在从未看过float
的浮点寄存器中。printf
三元运算符遵循语言规则来决定其结果的类型。它有时不能是整数,有时不能是浮点数。这些可能是不同的大小,存储在不同的位置等等,这将导致无法生成处理这两种可能结果类型的健全代码。
这是一个猜测。也许正在发生一些完全不同的事情。未定义的行为未定义是有原因的。如果没有大量的平台和编译器细节的经验和知识,这些事情可能无法预测并且很难理解。永远不要让某人说服你 UB 是好的或安全的,因为它似乎在他们的系统上工作。
因为您正在使用%d
打印float
值。使用%f
. 用于%d
打印float
值会调用未定义的行为。
编辑:关于OP的评论;
为什么它打印随机数而不是 0?
当你编译这段代码时,编译器应该给你一个警告:
[Warning] format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat]
这个警告是不言自明的,这行代码正在调用未定义的行为。这是因为,转换规范%d
指定printf
将int
值从二进制转换为十进制数字字符串,而%f
对float
值执行相同操作。在传递fvalue
编译器时,它知道它是float
type ,但另一方面它看到它printf
需要一个 type 的参数int
。在这种情况下,有时它会达到您的预期,有时它会达到我的预期。有时它会做没有人预料到的事情(David Schwartz的好评论)。
请参阅测试用例1和2。它与%f
.
它应该进行隐式类型转换吗?
不。
尽管现有的赞成答案是正确的,但我认为它们过于技术性并且忽略了初学者程序员可能拥有的逻辑:
让我们看一下引起一些人困惑的陈述:
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
。他同意fvalue
beeing afloat
和0
一个整数。但是,他了解到,三元运算符的不同可能结果必须是同一类型!int
而float
不是。在这种情况下,“ float
wins”0
变为0.0f
!
现在printf
不是类型安全的。这意味着您可以错误地说“打印我一个整数”并传递一个浮点数而编译器不会注意到。正是那件事发生了。无论 的值test
是什么,编译器都会推断出结果的类型是float
。因此,您的代码相当于:
float x = 0.0f;
printf("%d", x);
此时,您会遇到未定义的行为。根本float
不是.integral
%d
观察到的行为取决于您使用的编译器和机器。您可能会看到跳舞的大象,尽管大多数终端不支持那个 afaik。
当我们有表达式E1 ? E2 : E3
时,涉及到四种类型。Expressions E1
,E2
并且E3
每个都有一个类型(并且 和 的类型E2
可以E3
不同)。此外,整个表达式E1 ? E2 : E3
有一个类型。
如果E2
和E3
具有相同的类型,那么这很容易:整个表达式具有该类型。我们可以用这样的元符号来表达这一点:
(T1 ? T2 : T2) -> T2
"The type of a ternary expression whose alterantives are both of the same type T2
is just T2."
如果它们不具有相同的类型,事情就会变得有些有趣,并且情况与算术运算非常相似E2
并E3
一起参与。例如,如果将 and 相加int
,float
则int
操作数将转换为float
。这就是您的程序中正在发生的事情。类型情况是:
(int ? float : int) -> float
测试失败,因此将int
value0
转换为float
value 0.0
。
此值与 的转换说明符float
不兼容,后者需要.%d
printf
int
更准确地说,该float
值又经历了一次。当 afloat
作为尾随参数之一传递给可变参数函数时,它会转换为double
.
所以实际上double
值 0.0 被传递到printf
它期望的地方int
。
在任何情况下,它都是未定义的行为:它是 C 语言的 ISO 标准定义没有提供意义的不可移植代码。
从这里开始,我们可以应用特定于平台的推理,为什么我们不只是看到0
. 假设int
是 32 位、4 字节类型,并且double
是常见的 64 位、8 字节、IEE754 表示,并且全位为零用于0.0
. 那么,为什么这个全位零的 32 位部分不被printf
视为int
值0
呢?
很可能,64 位double
参数值在放入堆栈时强制 8 字节对齐,可能会将堆栈指针移动 4 个字节。然后printf
从这四个字节中提取垃圾,而不是从double
值中提取零位。