c++ - C语言中printf输出的奇怪错误
巴扎黑
巴扎黑 2017-04-17 11:42:34
0
2
673

源代码很简单,就是定义一个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的输出没有一个是对的?
本人是小白,希望能有大神帮忙解答,多谢!

巴扎黑
巴扎黑

全部回覆(2)
伊谢尔伦

首先,float在這裡會自動轉換為double, 為了方便理解,我們在這裡假設 sizeof(int) 是 4, sizeof(double) 是 8.

然後,針對這一句:

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

依照 %dn%fn%dn%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.

如標準所言,這是 UB (未定義行為),所以至於它為何會輸出你截圖裡的那四個數,可以不必深究。不同編譯器的結果也不盡相同。


補充

上面分析了原理,那麼也可以順帶分析一下你的第二句為什麼可以輸出你想要的。

依照

去讀,堆疊裡面的

資料%fn%dn%fn%d以及讀取的指標將呈現如下形式:

          |     |4
(float)a_>|_2.5_|4_ _ _ _ _ _ correct!
  (int)a_>|     |4
          |_2.5_|4
(float)b_>|__2__|4
  (int)b_>|__2__|4_ _ _ _ _ _ correct!
運氣不錯,牛頭正好對上了馬嘴,你得到了你想要的輸出。 。 。

不要僥倖,老實的按類型去

, 才是初學者正確的態度。

洪涛

你留意一下標準庫裡面的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 00 040000 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的在做輸出的時候會根據格式字串來獲取傳遞進來的不定參數,而錯誤的格式字串會導致不定參數的獲取錯誤,從而導致輸出錯誤。

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!