C中的三元运算符

时间:2013-09-28 05:03:51

标签: c

为什么此计划会提供意外数字(例如:2040866504-786655336)?

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

     return 0;
}

为什么要打印意外的数字而不是0?它应该做隐式的类型转换吗?这个程序用于学习目的并不严重。

4 个答案:

答案 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指定printfint值从二进制转换为十进制数字字符串,而%ffloat执行相同操作{1}}价值。在传递fvalue编译器时,知道它是float类型,但另一方面它看到printf期望类型为int的参数。在这种情况下,有时它会按照您的预期进行,有时它会达到我的预期。有时它会做到没人想要的David Schwartz的好评) 查看测试用例12。它与%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建立float0整数。但是,他了解到三元运算符的不同可能结果必须是同一类型! intfloat不是。在这种情况下,“float获胜”,0变为0.0f

现在printf不是类型安全的。这意味着你可以错误地说“打印一个整数”并传递一个浮点数而不需要编译器注意到。恰好发生了这种情况。无论test的值是什么,编译器都推断出结果的类型为float。因此,您的代码等同于:

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

此时,您会遇到未定义的行为。 float根本不是integral %d所期望的内容。

观察到的行为取决于您正在使用的编译器和计算机。你可能会看到跳舞的大象,虽然大多数终端都不支持这种大象。

答案 3 :(得分:1)

当我们有表达式E1 ? E2 : E3时,涉及四种类型。表达式E1E2E3各有一个类型(E2E3的类型可以不同)。此外,整个表达式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在算术运算中一起参与。例如,如果将intfloat加在一起,则int操作数将转换为float。这就是您的计划中发生的事情。类型情况是:

(int ? float : int) -> float

测试失败,因此int0转换为float0.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 printfint处理这个全比特零的32位部分?

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