C printf函数如何处理%f规范?
我有几个程序的输出我无法理解: 方案1 方案2 现在再举一个例子C printf函数如何处理%f规范?,c,floating-point,string-formatting,C,Floating Point,String Formatting,我有几个程序的输出我无法理解: 方案1 方案2 现在再举一个例子 #include <stdio.h> int main(void) { char i='a'; int a=5; printf("i is %d \n",i); // here %d type cast char value in int printf("a is %f \n",a); // hete %f dont typecast float value printf("a is %f \
#include <stdio.h>
int main(void)
{
char i='a';
int a=5;
printf("i is %d \n",i); // here %d type cast char value in int
printf("a is %f \n",a); // hete %f dont typecast float value
printf("a is %f \n",(float)a); // if we write (float) with %f then it works
return 0;
}
问题:
这是怎么回事?为什么要显示输出?编译器不会基于格式说明符(如
%f
)进行任何类型转换。每个参数都是以正常方式传递的,并且给出一个与其格式说明符不匹配的参数是程序中的一个错误,导致未定义的行为。首先,对于任何变量函数,例如printf()
,小于int
的类型的所有整数值都作为int
传递(或在某些平台上的某些情况下为unsigned int
),所有float
值作为double
传递。因此,您的(float)a
将第二次强制转换为double
其次,printf()
将尝试从参数列表中读取一个double
。接下来发生的是未定义的行为
一些编译器——特别是GCC——将报告文字字符串格式和相应的参数列表之间的类型不匹配。如果常规编译器不进行该分析,请考虑使用至少用于初步编译的编译器来整理编译错误。
printf("i is %d \n",i);
这里没什么奇怪的。字符a
是中的97号
a
是一个值为5的整数。在内存中,这将是字节[0x5 0x0 0x0 0x0]
。但这个命令指向printf。它说“请相信我,a
指向浮点”.printf相信你。浮点数很奇怪,但基本上它们的工作原理类似于科学记数法,但以2为基数,而不是以10为基数。随机情况下,将2.1688指定为浮点数的方式恰好是[0x5 0x0 0x0 0x0]
。因此printf向你展示了这一点
printf("a is %f \n",(float)a);
在这里,您已经告诉编译器,您希望在printf看到之前将a
转换为浮点。C编译器知道如何以奇怪的浮点格式将其转换为express5。因此printf得到了它期望的结果,您将看到5.0000
接下来的酷实验:想看看其他整洁的东西吗?试试看
union data {
int i;
float f;
char ch[4];
};
union data d;
d.i = 5;
printf("How 5 is represented in memory as an integer:\n");
printf("0x%X 0x%X 0x%X 0x%X\n", v.ch[0], v.ch[1], v.ch[2], v.ch[3]);
d.f = 5.0;
printf("How 5 is represented in memory as a float:\n");
printf("0x%X 0x%X 0x%X 0x%X\n", v.ch[0], v.ch[1], v.ch[2], v.ch[3]);
// OUTPUT:
// How 5 is represented in memory as an integer:
// 0x5 0x0 0x0 0x0
// How 5 is represented in memory as a float:
// 0x0 0x0 0xFFFFFFA0 0x40
实际上,您可以看到C编译器是如何为您更改周围的数据的(假设这样做有效…这更有趣。我在三台不同的linux机器上测试了它,并在第二行输出中得到了两个不同的结果:
gcc 4.5.1 32bit: a is -0.000000
gcc 4.5.1 -Ox 32bit: a is 0.000000
gcc 4.5.1 64bit: a is 0.000000
gcc 4.6.2 64bit: a is 0.000000
您可能正在64位x86体系结构上运行此应用程序。在此体系结构上,浮点参数在XMM寄存器中传递,而整数参数在通用寄存器中传递。请参阅 因为
%f
需要一个浮点值:
printf("i is %f \n",i);
打印XMM0寄存器中的值,该值恰好是先前分配的k
的值,而不是在RSI寄存器中传递的i
。程序集如下所示:
movl $.LC1, %edi # "k is %f \n"
movsd .LC0(%rip), %xmm0 # float k = 2
call printf
movl $1, %esi # int i = 1
movl $.LC2, %edi # "i is %f \n"
call printf # prints xmm0 because of %f, not esi
int i = 1;
printf("i is %f \n",i);
float k = 2;
printf("k is %f \n",k);
如果按如下方式重新排列作业:
movl $.LC1, %edi # "k is %f \n"
movsd .LC0(%rip), %xmm0 # float k = 2
call printf
movl $1, %esi # int i = 1
movl $.LC2, %edi # "i is %f \n"
call printf # prints xmm0 because of %f, not esi
int i = 1;
printf("i is %f \n",i);
float k = 2;
printf("k is %f \n",k);
它打印:
i is 0.000000
k is 2.000000
因为XMM0寄存器的值恰好为0
[更新]
它也可以在32位x86上复制。在此平台上,printf()
基本上是将int*
转换为double*
,然后读取该double
。让我们修改示例以使其易于查看:
int main() {
float k = 2;
int i = -1;
printf("k is %f \n",k);
printf("i is %f \n",i,i);
}
64位输出:
k is 2.000000
i is 2.000000
k is 2.000000
i is -nan
32位输出:
k is 2.000000
i is 2.000000
k is 2.000000
i is -nan
也就是说,值为-1的2
int
s看起来像是一个double
0xffffffffff,它是一个NaN
值。IIRC使用错误的类型说明符在C中是未定义的。我刚刚用OPs第一个示例尝试了排列。我注意到,当您使用带有参数o的声明和printf按特定顺序排列时当然,输出(即打印的值)是相同的。对于所有其他情况,int上带有%f的printf打印0(在两个不同的编译器上测试)。虽然我完全同意结果是未定义的,但相同的输出确实引起了一些注意。就词汇表而言,在C中是“cast”表示使用强制转换语法的显式转换,就像您在第二个示例中所做的那样,(float)
。因此强制转换总是由您自己编写的。printf()进行类型转换请参见printf(“i是%d\n”,i);这会将char n值打印到intNo中,但不会。这是参数传递中的正常类型提升,其中接收类型未知,并且在每个变量函数和每个没有原型的函数中都会发生。即使是%c
格式说明符也需要int
参数(然后它将其解释为好像它是从char
提升的)。@Mr.32,这称为整数提升,与类型转换不同。什么?是[0x5 0x0 0x0]
是用大尾数还是小尾数写的?小尾数,这意味着最小的地址有最小的有效字节。但是您的里程可能会有所不同。在您的机器上编译它,看看会发生什么…我认为您忽略了一个事实,%f
不是用于float
,而是用于双参数。对于va_arg funct所有的float
在传递到函数之前都会被静默地转换为double
。谢谢,我想说“这是未定义的行为”太容易了。)我可以在32位x86架构上重现这一点