2013-09-28 136 views
13

为什么这个程序给出了意想不到的数字(例如:2040866504,-786655336)?三元运算符C

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

    return 0; 
} 

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

+0

你是什么意思的“随机”?你试图将一个浮点数作为一个整数来打印,但是这个数字应该是'0',它们在两种类型中都有相同的位表示。 –

+1

随机,或只是不正确? –

+0

这个问题似乎是脱离主题,因为它是关于谁知道什么。 – 2013-09-28 05:08:33

回答

18

最有可能的,平台通行证在一个不同的寄存器浮点值的浮点寄存器和整数值(或在堆栈上)。你告诉printf寻找一个整数,所以它看着寄存器整数传入(或在堆栈中)。但是你通过了一个float,所以零被放置在printf永远不会看到的浮点寄存器中。

三元运算符遵循语言规则来决定其结果的类型。它有时不能是一个整数,有时候是一个浮点数。这些可能是不同的大小,存储在不同的地方等等,这将使得不可能生成合理的代码来处理可能的结果类型。

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

+0

这个答案中唯一缺少的东西就是为什么参数是浮点数。 –

+0

我在中段添加了更多关于这方面的信息。 –

+0

哇...很好的解释!现在这个概念很清楚:-) – Tonmoy

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编译器知道它是float类型,但另一方面它看到printf需要int类型的参数。在这种情况下,有时会达到您的预期,有时会达到我的预期。有时它没有人期望(由David Schwartz尼斯评论)。
查看测试案例12。它与%f工作正常。

它应该做隐式类型转换吗?

+0

的确如此,但我认为这与此处显示的特定代码无关。 –

+0

为什么打印随机数而不是0?它应该做隐含的类型转换吗? – Tonmoy

+4

@MarkRansom;为什么?请参阅测试用例[1](http://ideone.com/Ig5uub)和[2](http://ideone.com/woe4XU)。 – haccks

8

虽然现有upvoted答案是正确的,我觉得他们都太技术而忽略逻辑初学者程序员可能有:

让我们来看看本声明的一些首脑造成混乱:

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预计什么。

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

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转换说明符printf不兼容,这需要int

更确切地说,float值经历了一次。当float作为可变参数函数的尾随参数之一传递时,它将转换为double

所以实际上double值0.0正在传递到printf它期望int

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

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

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