如何在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; } 

有人可以解释为什么从double / float到int的转换在第一种情况下正常工作,并且在printf中完成时不起作用?
该程序是在32位linux机器上的gcc-4.1.2上编译的。


编辑: Zach的答案似乎是合乎逻辑的,即使用格式说明符来确定从堆栈弹出的内容。 但是请考虑这个后续问题:

 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; } 

这是如何运作的?

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的整数提升规则表示小于int所有类型在作为vararg传递时都会被提升为int 。 所以在你的情况下, 'd'被提升为int ,然后printf弹出一个int并转换为char 。 我能找到的这种行为的最佳参考是这篇博客文章 。

没有“在printf转换为int ”这样的东西。 printf不做,也不能做任何铸造。 格式说明符不一致会导致未定义的行为。

实际上, printf只接收原始数据并将其重新解释为格式说明符隐含的类型。 如果你传递一个double值并指定一个int格式说明符(如%d ), printf将获取该double值并盲目地将其重新解释为一个int 。 结果将是完全不可预测的(这就是为什么这样做正式导致C中的未定义行为)。

杰克的回答解释了如何解决问题。 我将解释为什么你会得到意想不到的结果。 您的代码相当于:

 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的实际转换在生成的程序集中要复杂得多。

因为您没有使用浮点格式说明符,请尝试使用:

 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); 

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

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

值得注意的是,作为具有可变长度参数列表的函数的printf从不接收浮点数; 浮动参数是“老派”升级为双打。

最近的标准草案首先介绍了“旧学校”的默认促销(n1570,6.5.2.2/6):

如果表示被调用函数的表达式具有不包含原型的类型,则对每个参数执行整数提升,并将具有float类型的参数提升为double。 这些被称为默认参数促销。

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

函数原型声明符中的省略号表示法导致参数类型转换在最后声明的参数之后停止。 默认参数提升是在尾随参数上执行的。

printf的结果是不可能“打印”真正的浮动。 浮点表达式始终提升为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格式表示的值; 它接近1.122E1,即预期结果11.22。