2010-03-08 92 views
10

考虑这个计划如何在printf中处理float/double转换为int?

int main() 
{ 
     float f = 11.22; 
     double d = 44.55; 
     int i,j; 

     i = f;   //cast float to int 
     j = d;   //cast double to int 

     printf("i = %d, j = %d, f = %d, d = %d", i,j,f,d); 
     //This prints the following: 
     // i = 11, j = 44, f = -536870912, d = 1076261027 

     return 0; 
} 

有人可以解释为什么从双/浮到INT铸造正常工作在第一种情况下,和在printf中完成时不起作用?
该程序是在32位Linux机器上的gcc-4.1.2上编译的。


编辑: Zach's answer似乎是合乎逻辑的,即使用格式说明,以弄清楚如何弹出堆栈。然后考虑这个后续问题:

int main() 
{ 

    char c = 'd'; // sizeof c is 1, however sizeof character literal 
        // 'd' is equal to sizeof(int) in ANSI C 

    printf("lit = %c, lit = %d , c = %c, c = %d", 'd', 'd', c, c); 
    //this prints: lit = d, lit = 100 , c = d, c = 100 
    //how does printf here pop off the right number of bytes even when 
    //the size represented by format specifiers doesn't actually match 
    //the size of the passed arguments(char(1 byte) & char_literal(4 bytes))  

return 0; 
} 

这是如何工作的?

+0

我有类似的疑问。看到这个线程:http://stackoverflow.com/questions/2377733/how-does-this-program-work – Lazer 2010-03-08 03:16:14

+1

字符是一个单一的字符 - 它只是一个8位整数。当你对整型小于int的任何类型的操作时,它们会被提升为整数。这包括调用函数时。所以实际上它不是随机的机会导致你的printf调用工作,这种行为是定义的。 实际上,在大多数C ABI中,您始终为堆栈上传递的每个变量分配至少一个机器字。 – 2016-01-22 16:00:04

+0

@SurajJain是,''参数传递只考虑根据默认参数促销推广的类型(C11§6.5.2.2/ 6,§7.16.1.1/ 2),它确保'char'和'int'兼容。然而,这与用于算术的促销不完全相同。另外,就ABI而言,推断语言是很危险的。确认它确实需要检查规则。 – Potatoswatter 2016-08-23 05:04:26

回答

19

printf函数使用格式说明符来确定弹出堆栈的内容。所以当它看到%d时,它弹出4个字节并将它们解释为int,这是错误的((float)3.0的二进制表示与(int)3不同)。

您需要使用%f格式说明符或将参数转换为int。如果您使用的gcc一个新的足够的版本,那么强烈的警告打开捕获这种错误:

$ gcc -Wall -Werror test.c 
cc1: warnings being treated as errors 
test.c: In function ‘main’: 
test.c:10: error: implicit declaration of function ‘printf’ 
test.c:10: error: incompatible implicit declaration of built-in function ‘printf’ 
test.c:10: error: format ‘%d’ expects type ‘int’, but argument 4 has type ‘double’ 
test.c:10: error: format ‘%d’ expects type ‘int’, but argument 5 has type ‘double’ 

回答这个问题的编辑部分:

C'S整数提升规则说,所有的当作为可变参数传递时,小于int的类型被升级为int。所以在你的情况下,'d'被升级到int,然后printf弹出一个int并铸造为char。我能找到的最佳参考是this blog entry

+3

+1我喜欢强烈的警告。 – 2010-03-08 01:50:06

+0

我有一个后续问题,请参阅我的问题的编辑部分。如果问题不明确,请发表评论...谢谢 – Sandip 2010-03-08 02:45:37

+1

当参数列表的变量部分传递时,'char'参数被提升为'int'的确是这样(这是在'c'传入例如),但是字符常量常量(如''d'')已经是'int'类型。 (注意在理论上,在某些编译器中,可以将'char'参数提升为'unsigned int')。 – caf 2010-03-08 04:21:44

2

因为您不使用浮动格式说明,尝试用:

printf("i = %d, j = %d, f = %f, d = %f", i,j,f,d); 

否则,如果你想4个整数,你必须传递参数给printf之前丢掉。

printf("i = %d, j = %d, f = %d, d = %d", i,j,(int)f,(int)d); 
0

printf使用变长参数列表,这意味着您需要提供类型信息。你提供的信息是错误的,所以会让你感到困惑。 Jack提供了实用的解决方案。

4

Jack's answer说明如何解决您的问题。我要解释为什么你会得到意想不到的结果。您的代码就相当于:

float f = 11.22; 
double d = 44.55; 
int i,j,k,l; 

i = (int) f; 
j = (int) d; 
k = *(int *) &f;   //cast float to int 
l = *(int *) &d;   //cast double to int 

printf("i = %d, j = %d, f = %d, d = %d", i,j,k,l); 

的原因是fd传递给printf的值,然后这些值被解释为int秒。这不会更改二进制值,因此显示的数字是floatdouble的二进制表示形式。在生成的组件中,从floatint的实际演员要复杂得多。

+2

看到我的答案可能会帮助他理解他的输出,但代码是不相同的。你不能为错误解释printf或任何可变参数函数编写等价代码,同时仍然调用可变参数函数。 – 2010-03-08 01:59:19

+0

我的代码确实(对我来说,也许对他来说)他的代码在做什么(对他来说)。两者都是未定义的行为,所以任何人都不能保证特别做任何事情。 – 2010-03-08 02:27:16

7

有因为没有这样的事情“铸造intprintf”。 printf不会做,也不能做任何演员。不一致的格式说明符导致未定义的行为。

实践中printf只是接收原始数据而重新解释它作为格式说明符所暗示的类型。如果您通过double值并指定int格式说明符(如%d),则printf将采用该值double,并盲目地将其重新解释为int。结果将是完全不可预测的(这就是为什么在C中正式导致未定义的行为)。

+0

当然可以施展 - 为什么不呢?所有的信息都在那里。编译器比我们更了解参数类型(是否有人提到默认促销?),并且转换说明符是标准的一部分。它没有正确转换的原因部分是历史性的(原来的C编译器对于这种分析过于简单),现在改变甚至可能破坏依赖于重新解释的代码。 – 2016-02-23 17:57:08

+0

@彼得答:施耐德:真的吗?格式字符串是运行时值的情况怎么样?即使“转换说明符是标准的一部分”,您如何期望编译器“投射”任何东西?更一般地说,这是C语言设计的一个基本原则:它的标准库不依赖于编译器的“神奇”功能(可能只有少数例外)。实际上,每个库功能都可以由用户使用C语言本身来实现。只要C语言遵循这个基本原则,'printf'不会被任何编译器魔术所“增强”。 – AnT 2016-02-23 18:18:13

+0

不可否认,我没有想到运行时格式化字符串(因为在所有的现实中:你最后一次使用它的时间是?)。但标准可以区分这些情况 - 看看他们对“sizeof”做了什么。至于演员:很明显,编译器知道如何投射类型,所以我无法在这里检测到任何额外的魔法;而且我也认为演员可能可以用C来实现。就像一个大的if/else如果切换转换运算符,然后转换适当的强制转换。突破变化宁愿成为省略号原型的变化语义。 – 2016-02-24 08:30:31

1

您的后续代码工作的原因是因为它被压入堆栈之前的字符常量被提升到一个int。所以printf会弹出4个字节的%c和%d。实际上,字符常量的类型是int,而不是char类型。 C很奇怪。

0

值得注意的是printf,是与可变长度的参数列表的功能,从不接收浮子;浮动论据是“老派”晋升为双打。

最近的一项标准草案首先介绍了 “老派” 默认促销(n1570,6.5.2.2/6):

If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.

然后讨论可变参数列表(6.5.2.2/7):

The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

printf的后果是,它是不可能的“打印”一个真正的浮动。 float表达式总是被提升为double,这是IEEE 754实现的8字节值。此促销发生在主叫方;开始执行时,printf在堆栈上已经有一个8字节的参数。

如果我们将11.22分配给double并检查其内容,使用我的x86_64-pc-cygwin gcc,我看到字节序列000000e0a3702640。

这解释了由printf打印的int值:该目标上的Ints仍然有4个字节,因此只有前四个字节0​​00000e0被评估,并且再次以小端排序,即为0xe0000000。这是十进制的-536870912。

如果我们反向所有8个字节的,因为英特尔处理器将在小尾数双打,我们也得到402670a3e0000000。我们可以检查这个字节序列在IEEE格式on this web site中表示的值;接近于1.122E1,即预期的结果是11.22。