C printf函数如何处理%f规范?

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 \

我有几个程序的输出我无法理解:

方案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 \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架构上重现这一点