C语言中printf输出的奇怪错误

源代码很简单,就是定义一个float变量a=2.5,int变量b=2

然后将a,b分别按%d %f型输出。
当然没有按正确类型格式化输出肯定是有问题的。
但是按正确类型格式化输出也发生了很奇怪的问题。
源代码如下,VS2013下编译通过0 errors, 0 warnings。

#include <stdio.h>
int main(void)
{

float a = 2.5;
int b = 2;


printf("%d\n%f\n%d\n%f\n", a, a, b, b);    /*这条语句的输出全是错的*/
putchar('\n');

printf("%f\n%d\n%f\n%d\n", a, a, b, b);    /*这条语句的输出有两个对的两个错的,符合我的预期*/

return(0);}

输出结果如下图
图片描述

我的主要问题是,为什么第一个printf的输出没有一个是对的?
本人是小白,希望能有大神帮忙解答,多谢!

阅读 18.4k
2 个回答

首先,float在这里会自动转换为double, 为了方便理解,我们在这里假设 sizeof(int) 是 4, sizeof(double) 是 8.

然后,针对这一句:

printf("%d\n%f\n%d\n%f\n", a, a, b, b);

依照 %d\n%f\n%d\n%f 去读,栈里面的数据以及读取的指针将呈现如下形式:

  (int)a_>|     |4
          |_2.5_|4
(float)a_>|     |4
  (int)b_>|_2.5_|4
          |__2__|4
(float)b_>|__2__|4

所以呢,第一个 %d 读到的实际是 2.5 的前 4 byte。 紧接着第一个 %f 读的是 2.5 的后 4 byte, 加上下一个 2.5 的前 4 byte. 同理,第二个 %d 读到的是 2.5 的后 4 byte, 而第二个 %f 读到的是两个 2.

好吧,是不是有点乱。C 语言中是如何定义这种在输出里"类型乱读"的现象呢?

7.21.6 Formatted input/output functions:

If a conversion specification is invalid, the behavior is undefined.282) If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

如标准所言,这是 UB (未定义行为),所以至于它为何会输出你截图里的那四个数,可以不必深究。不同编译器的结果也不尽相同。


补充

上面分析了原理,那么也可以顺带分析一下你的第二句为什么可以输出你想要的。

依照 %f\n%d\n%f\n%d 去读,栈里面的数据以及读取的指针将呈现如下形式:

          |     |4
(float)a_>|_2.5_|4_ _ _ _ _ _ correct!
  (int)a_>|     |4
          |_2.5_|4
(float)b_>|__2__|4
  (int)b_>|__2__|4_ _ _ _ _ _ correct!

运气不错,牛头正好对上了马嘴,你得到了你想要的输出。。。

不要侥幸,老老实实的按类型去 printf, 才是初学者正确的态度。

你留意一下标准库里面的stdarg.h文件,它里面定义了几个宏,分别是va_startva_argva_end,这几个宏都是用来做不定参数传递的。特别注意的是va_arg在获取传递进来的参数时依赖它的第二个参数(t,类型),如果类型不对应的话会导致整个不定参数的传递没法正常的解析(错位)。prinft依赖于vsprintf,而vsprintf的实现依赖这几个宏,它会根据格式字符串(也就是那些%d %f ...)来调用va_arg,如果你的格式字符串和你后面的不定参数没有正确对应起来,那么也有可能在获取不定参数时出现错位(而你第二个输出对了两次也是因为错位了两次之后刚好又对上了的缘故)。

还有float(32位)参数在传递printf的时候会自动转换成double(64位)(这也是%f和%lf没区别的缘故),int参数占32位。

有了上面的铺垫之后,下面来解释你的这个问题。
环境:Windows XP 32, gcc version 3.4.0 (mingw special)

注:以下连续的十六进制数据从左到右对应于内存地址的由低到高

你传入的参数a,a,b,b,对应十六进制的00 00 00 00 00 00 04 40 00 00 00 00 00 00 04 40 02 00 00 00 02 00 00 00正确的分割应该是:

a: [00 00 00 00 00 00 04 40]
a: [00 00 00 00 00 00 04 40]
b: [02 00 00 00]
b: [02 00 00 00]

根据你的第一次输出的格式字符串,printf做了如下的分割:

%d: [00 00 00 00] int,对应0x00000000,也就是0了
%f: [00 00 04 40 00 00 00 00] double,0.0,(看double的表示法)
%d: [00 00 04 40] int,对应0x40040000,也就是10进制的1074003968
%f: [02 00 00 00 02 00 00 00] double,0.0

所以输出也就是:

0
0.000000
1074003968
0.000000

而根据你第二次输出的格式字符串,printf做了如下的分割:

%f: [00 00 00 00 00 00 04 40] double, 对应2.5
%d: [00 00 00 00] int 对应0
%f: [00 00 04 40 02 00 00 00]   double,对应0
%d:[02 00 00 00] int 对应0

也就是你看到的:

2.500000
0
0.000000
2

至于为什么好几次double都变成0.0,你需要去看看IEEE的double表示标准。

简而言之就是:printf的在做输出的时候会根据格式字符串来获取传递进来的不定参数,而错误的格式字符串会导致不定参数的获取错误,从而导致输出错误。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏