为什么此计划会提供意外数字(例如:2040866504
,-786655336
)?
#include <stdio.h>
int main()
{
int test = 0;
float fvalue = 3.111f;
printf("%d", test? fvalue : 0);
return 0;
}
为什么要打印意外的数字而不是0
?它应该做隐式的类型转换吗?这个程序用于学习目的并不严重。
答案 0 :(得分:18)
最有可能的是,您的平台在浮点寄存器中传递浮点值,在不同寄存器(或堆栈)中传递整数值。你告诉printf
寻找一个整数,所以它在寄存器中查找整数(或在堆栈中)。但是你传递了float
,所以零被置于printf
从未查看的浮点寄存器中。
三元运算符遵循语言规则来决定其结果的类型。它有时不能是整数,有时候是浮点数。这些可能是不同的大小,存储在不同的地方,等等,这将导致无法生成合理的代码来处理两种可能的结果类型。
这是猜测。也许正在发生一些完全不同的事情。由于某种原因未定义未定义的行为。如果没有丰富的平台和编译器细节经验和知识,这些事情是无法预测的,也很难理解。永远不要让别人说服UB是好的或安全的,因为它似乎适用于他们的系统。
答案 1 :(得分:11)
因为您使用%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
执行相同操作{1}}价值。在传递fvalue
编译器时,知道它是float
类型,但另一方面它看到printf
期望类型为int
的参数。在这种情况下,有时它会按照您的预期进行,有时它会达到我的预期。有时它会做到没人想要的(David Schwartz的好评)
查看测试用例1和2。它与%f
一起正常工作。
它应该做隐式的类型转换吗?
没有。
答案 2 :(得分: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
的价值。他同意fvalue
建立float
和0
整数。但是,他了解到三元运算符的不同可能结果必须是同一类型! int
和float
不是。在这种情况下,“float
获胜”,0
变为0.0f
!
现在printf
不是类型安全的。这意味着你可以错误地说“打印一个整数”并传递一个浮点数而不需要编译器注意到。恰好发生了这种情况。无论test
的值是什么,编译器都推断出结果的类型为float
。因此,您的代码等同于:
float x = 0.0f;
printf("%d", x);
此时,您会遇到未定义的行为。 float
根本不是integral
%d
所期望的内容。
观察到的行为取决于您正在使用的编译器和计算机。你可能会看到跳舞的大象,虽然大多数终端都不支持这种大象。
答案 3 :(得分:1)
当我们有表达式E1 ? E2 : E3
时,涉及四种类型。表达式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
在算术运算中一起参与。例如,如果将int
和float
加在一起,则int
操作数将转换为float
。这就是您的计划中发生的事情。类型情况是:
(int ? float : int) -> float
测试失败,因此int
值0
转换为float
值0.0
。
此float
值与%d
的{{1}}转换说明符不兼容,后者需要printf
。
更确切地说,int
值经历了一次。当float
作为可变参数函数的尾随参数之一传递时,它将转换为float
。
事实上,double
值0.0传递给double
,预期printf
。
在任何情况下,它都是未定义的行为:它是不可移植的代码,C语言的ISO标准定义没有提供含义。
从现在开始,我们可以应用特定于平台的推理,为什么我们不仅仅看到int
。假设0
是32位,四字节类型,int
是公共64位,8字节,IEE754表示,并且全位0用于double
。 那么,为什么0.0
printf
值int
处理这个全比特零的32位部分?
很可能,64位0
参数值在将其放入堆栈时强制进行8字节对齐,可能会将堆栈指针移动4个字节。然后double
从这四个字节中抽出垃圾,而不是printf
值中的零位。