1. 程式人生 > >c語言格式輸出剖析——用%d輸出float型別資料與int型別%f格式輸出

c語言格式輸出剖析——用%d輸出float型別資料與int型別%f格式輸出

C語言學習實踐

摘要

    本文將從C語言變數的本質,不同型別變數在記憶體中的儲存方式,型別強制轉換,格式輸出4個方面闡述C語言初學階段的一些問題。

關鍵詞:記憶體儲存,型別強制轉換,反彙編

1. 變數

    變數來源於數學,是計算機語言中能儲存計算結果或能表示值抽象概念。在諸如C語言等高階語言中,變數的使用遮蔽了資料的底層細節,使得高階語言程式設計師不必像彙編程式設計師那樣關心資料與硬體之間的關係。為了探究C語言中變數在記憶體中的儲存形式,可以藉助反彙編檢視組合語言以及記憶體資料。

2. 變數在記憶體中的儲存形式

    在記憶體中,無論哪種資料型別的資料,都是以相應長度的二進位制碼存取。從記憶體取資料是,如果不按照定義資料型別的方式取資料,所取資料就會錯誤。

2.1   用反彙編檢視變數記憶體資料

(1)   實驗程式碼如下。在賦值部分打點後除錯,轉入反彙編。

 


(2)   在監視視窗檢視變數的記憶體地址,並在記憶體視窗中檢視資料。


    整形變數_4ByteData的資料在以記憶體地址0x0023FA58起始的四個Byte中存放:【注意】 Intel處理器是小端機,資料高位在高地址,地位在地址。所存資料:4Bytes的十六進位制數 0x12345678

 

    單精度浮點型變數fl的資料在記憶體中的儲存:


    雙精度浮點型變數df的資料在記憶體中的儲存:

 

    字元型變數ch的資料在記憶體中儲存;

 

結論:

    (1)   區域性變數儲存在函式棧中,且該棧向低地址生長,所以先定義的區域性變

量在較高記憶體地址(比如_4ByteData在0x0023FA58,ch在0x0023FA33),並且區域性變數之間並非緊密排布,而是由8個Byte 的cc資料隔開。變數周圍塞些CCCCCCCC,這可能是編譯器提供的一種保護機制,越界了好出斷言。通過網上查詢資料,這是VC在Debug時給變數留出空間,用來檢查stack overflow。

    用release除錯,就不會有多餘的cc。(但是注意這裡char型變數與之前的區域性變數之間仍有資料,我猜測這裡是應為有變數對齊的緣故,char型變數也佔了4byte,只不過多餘的3byte由其他資料填充。)

 

    (2)   通過觀察不同型別的變數在記憶體中的儲存情況,可以發現:

在32位機器,VS2010 IDE中,一個int型資料佔4B,float型變數佔4B,double型變數佔8B,char型佔1B。

2.2    IEEE754 單精度數的格式

單精度浮點數佔據4個位元組,4個位元組的分配如下:

(a)第一位為符號位,0表示正,1表示負;

(b)第2~9位為階碼,採用移碼錶示;

(c)第10~32位為尾數,採用原碼錶示。

    給定32位串,如何轉換成十進位制數:

    假設記憶體中存在32位串:00 00 00 3f,因為INTELCPU採用little endian儲存方式,所以其真實的值為:

0x 3f 00 00 00。將其寫成二進位制形式:

    (1)第一步,化為二進位制

    0 01111110 0000000 00000000 00000000

    (2)第二步

    該浮點數為正數,階碼 01111110,移碼錶示(126-127) = -1

    尾數 0000000 00000000 00000000

    因為在IEEE754中,單精度浮點數有規格化處理,所以其真正尾數部分為

    1.0000000 00000000 00000000,其中‘.’為小數點

(3)   第三步

    根據公式寫出實際數值大小

    0.10000000 00000000 0000000 化為二進位制:0.5

2.3    IEEE754 雙精度數的格式

    長實數也稱雙精度數符號位1位,階碼11位,尾數52位

    給定32位串,如何轉換成十進位制數:

    假設記憶體中存在64位串:00 00 00 0000 00 e0 3f,因為INTEL CPU採用littleendian儲存方式,所以其真實的值為:       0x 3f e0 00 00 00 00 00 00。將其寫成二進位制形式:

(1)第一步

    0 01111111110 0000 00000000 00000000 00000000 00000000 0000000000000000

(2)第二步

    該浮點數為正數,階碼 01111111110,移碼錶示(1022-1023) = -1 尾數 0

    因為在IEEE754中,單精度浮點數有規格化處理,所以其真正尾數部分為

    1.0,其中‘.’為小數點

(3) 第三步

    根據公式寫出實際數值大小

    0.10 化為二進位制:0.5

3. 格式化輸出

3.1 printf函式呼叫的一般形式

    printf函式是一個標準庫函式,它的函式原型在標頭檔案“stdio.h”中。但作為一個特例,不要求在使用 printf 函式之前必須包含stdio.h檔案。printf函式呼叫的一般形式為:printf(“格式控制字串”, 輸出表列)。

    其中格式控制字串用於指定輸出格式。格式控制串可由格式字串和非格式字串兩種組成。格式字串是以%開頭的字串,在%後面跟有各種格式字元,以說明輸出資料的型別、形式、長度、小數位數等。如:

    “%d”表示按十進位制整型輸出;

    “%ld”表示按十進位制長整型輸出;

    “%c”表示按字元型輸出等

    非格式字串原樣輸出,在顯示中起提示作用。輸出表列中給出了各個輸出項,要求格式字串和各輸出項在數量和型別上應該一一對應

3.2 型別不對應下的格式輸出

_4ByteData = 0x12345678;  //Hexadecimal:0x12345678 對應Decimal:305419896

    fl = 0.5;

    df = 0.5;

    ch = 65;

    printf("%d\n",_4ByteData);

    printf("%c\n",_4ByteData);

    printf("%d\n",ch);

    printf("%f\n",&_4ByteData);

    printf("%d\n",fl);

    printf("%d\n",df);


分析:

可以看到同樣是int型變數,printf("%d\n",_4ByteData);與

printf("%c\n",_4ByteData);其結果分別為305419896(0x12345678對應的十進位制數),而後者只取了4位元組資料的最低一個位元組0x78,所以打印出了AISII碼0x78對應的字元’x’。

   聯絡C語言中指標的用法,我做出假設:格式輸出函式printf()根據型別字元%以及變數名,就可以根據資料首地址+讀取長度的方式輸出資料。

進一步發現:

    但是對字元型變數ch使用型別字元%d輸出,得到的是其ASCII碼的十進位制數,如果按照上述假設,會輸出以ch地址起始的4B的資料(這將是一個錯誤資料)。

但實驗結果是正確輸出了ch字元的ACSII碼的十進位制數。

再對浮點數做實驗:

    用型別字元%d輸出單精度數,雙精度數,結果均為0。

    由上文2.1可知單精度浮點變數fl(十進位制0.5)在記憶體中佔4B,機器碼是

0x00 00 00 3f(小端機)。現對整數_4ByteData賦值0x3f000000,並使用型別字元%f對該整數輸出,檢視結果:


 

    結果仍是0。這說明即使記憶體中資料儲存的內容一樣,但是使用型別字元%f對整型變數輸出,其結果仍然不是浮點數!

    綜上,原假設值得懷疑!

4. printf()函式型別不對應下的格式輸出進一步研究

4.1用%d輸出float型別資料

    float fl=0.5;如果用printf("%d",fl);輸出的是0。 但float型用%d輸出是否一定是0呢,答案肯定不都是0(如下圖)。

 

為什麼 0.5 用%d輸出的是0?

分析如下:

    首先來了解下printf的輸出格式,int 和 long int 都是32位的,用%d輸出;float 、double都是%f輸出,但 float 是32位的,double 是64位的,所以在引數傳遞的時候C語言統一將 float 型別數值傳換為double 型別再傳入 printf 函式。如果是32位整型則輸出格式為%lld。

下面來講一下  float fl=0.5f ;printf("%d",fl)輸出為0的情況:

     %d只輸出低32位的資料,並將這些32位二進位制以十進位制數輸出,編譯器首先將 0.5從float型別轉換為double型別,0.5在記憶體中的存放方式是0x3f000000,轉換成double型別在記憶體中的資料就是這個0x3fe0000000000000,這個記憶體資料可以很明顯看出低32位全是0,而%d則只能擷取到低32位,所以這個以%d輸出0.5的數值當然是 0了。如大家不相信可以用%lld 輸出看看,這個%lld就很讀到低64位資料,讀出的結果就是0x3fe0000000000000,在螢幕上看到一個很大的十進位制數。(這裡用%llx顯示十六進位制數更直觀)


如果我一定要輸出0.5在記憶體中的存放方法怎麼辦呢?

可以用printf("%d",*(int *)&fl);這裡做了一下處理,不是直接把fl傳進來,把fl所在地址裡的內容處理了一下,不管fl是什麼型別,只對地址進行操作,利用(int *)&lf,將fl所在地址中的內容0x3f000000直接當成 int 型別傳給printf,int 的型別資料不會再轉成double型別了,所以輸出正常,這個只是針對浮點型資料只佔低32位,如果輸出64位還得用%lld格式控制輸出。

如果用printf("%d",(int)fl),輸出行不行?

    這個強制型別轉換隻針對fl的資料型別進行轉換,0.5轉換 int 型別是0,而上面的*(int *)&a,是對記憶體中的實際儲存資料進行操作,蔽開資料型別這一層面,只將這個資料0x3f000000直接轉成int型別輸出。而(int)fl,要先看fl的型別,C語言會根據所要資料型別,對記憶體儲存的資料進行改變,以便可以用int型別正確解析記憶體資料。

    如果用printf("%d",(float)fl),輸出什麼,輸出的是0,這個只是將fl的float型別還轉成float型別,還是要自動轉成doube型別,傳給printf函式。

為什麼float非要轉成double型別呢?

    因為printf格式控制浮點型輸出只有%f,所以統一按doube型別輸出,不像整型有32位的%d或%ld,64位的有%lld,這就將32位整型和64位整型用不同的格式控制分開了,而%f則沒有,所以printf輸出的浮點數其實是統一遍歷了64位記憶體,如果float傳入printf沒有進行轉換,那麼printf輸出高32位資料將不可預知,printf輸出結果也就不正確了,因此傳入printf的浮點數都會被編譯器隱含轉成double型別。

4.2 int型別%f格式輸出

如果定義了inta=0x3f000000;用printf("%f",a)輸出的結果是多少呢?

   答案是0,至少我們看的螢幕上顯示的是0.000000,實際值可不是0啊,只是我們顯示的精度只能有15位小數,而實際的資料可能很小很小,0.0000....000幾百個0後會有幾個有效資料。

我們分析一下:

    首先C語言把a傳進printf,因為a是整型,所以不會自動轉成double型資料,直接將0x3f0000000傳進printf,而%f尋的是64位記憶體,也就是把0x000000003f000000這個記憶體中的資料當成浮點型輸出來,那浮點型的資料是多少呢,又是怎麼儲存的呢?

64位浮點數的存放方式:

        63位                 62~52位                 51~0位

        1個符號位       11個階數                 52個尾數

        從0x000000003f000000來看:

       00000000

        1)符號位是0,表示正

        2)階數是0,用移碼錶示:0-1023 = -1023,

用指數表示:1.#*2^-1023,‘#’是代表尾數。

        3)尾數就是,0x000003f000000

        4)浮點二進位制表示

 1.000000000000000000000011 1111 000000000000000000000000*2^(-1023),2^-1023次方可想而知有多小!

    這就是為什麼我們的int型資料用%f輸出是0.000000的原因!

    如果把0.5的雙精度數對應的十六進位制數賦給long long型別變數,則可以輸出正確的小數:

 

5. 總結:

    通過以上實驗,我驗證了原假設基本正確

    格式輸出函式printf()根據型別字元%以及變數名,就可以根據資料首地址+讀取長度的方式輸出資料。

但是,還要注意其中的一些細節:

(1)用%d輸出float型別資料時,在引數傳遞的時候C語言統一將 float 型別數值傳換為 double 型別再傳入 printf 函式。而%d只擷取低32位資料,所以得到的數字不是相應浮點數的二進位制碼。

(2)int型別%f格式輸出,%f尋的是64位記憶體,所以輸出的資料可能很小(比如2^-1023),那麼結果是0.

    綜上,無論什麼型別的資料,都只是01二進位制資料。只要清楚其記憶體儲存機制,拿到資料首址+偏移量,就能正確操作該資料!

    最後,我想說C語言非常靈活,高階程式設計師也要熟悉組合語言,會使用反彙編這把“手術刀”在底層剖析程式,會有更深刻的認識!

相關推薦

c語言格式輸出剖析——%d輸出float型別資料int型別%f格式輸出

C語言學習實踐 摘要     本文將從C語言變數的本質,不同型別變數在記憶體中的儲存方式,型別強制轉換,格式輸出4個方面闡述C語言初學階段的一些問題。 關鍵詞:記憶體儲存,型別強制轉換,反彙編 1. 變數     變數來源於數學,是計算機語言中能儲存計算結果或能表示值抽象

C語言程序】讓戶輸入一句話,輸出這句話中每個單詞含有多少個字母

get mage 一句話 printf png es2017 urn bsp can #include <stdio.h>#define N 100 //宏定義,用N表示100 int main(int argc, char *argv[]) { int i

C語言實現判斷兩個陣列中是否有相同的元素,有就輸出“有”,沒有則輸出“沒有”

建立兩個陣列,讓第一個陣列中的元素依次與第二個陣列中的元素比較(想讓第一個陣列中的第一個元素與第二個陣列中每個元素比較),若找到相同的,則計數並且計數停止,若計數不為零,則有相同元素,否則沒有。test.c#include<stdio.h> int main()

C語言中printf%d輸出float型別資料,或以%f輸出int資料的結果

1.測試程式及結果 程式#include"stdio.h" int main() { float a = 7.5, b = 1.23, c = 1.24, d = 1.25; double a1 = 7.5, b1 = 1.23, c1 = 1.24, d1 = 1.

c語言 3種方法 求出0~999之間的所有 水仙花數 並輸出

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

C語言-根據輸入的三角形的三條邊判斷三角形的型別,並輸出它的面積和型別

思路:首先判斷所給的三條邊是否能夠組成三角形,若可以組成三角形,則判斷該三角形是什麼型別,並求三角形的面積。相關知識:三角形是由同一平面內不在同一直線上的三條線段‘首尾’順次連線所組成的封閉圖形。常見

浙大版《C語言程式設計(第3版)》題目集 - 習題11-5 指定位置輸出字串(20 分)

題目連結:點選開啟連結   題目大意:略。   解題思路:略。   AC 程式碼 char *match( char *s, char ch1, char ch2 ) { char *p=s, *h; int fst=1,

C/C++語言實現十進位制正整數轉化為2-16進位制的數並輸出

<h3>/<span style="font-size:18px;">/將一個十進位制的正整數轉化為H進位制並輸出--拓展 在此先研究對於正整數的進位制轉換 以及2-16的進位制與十進位制的轉換 #include <IOSTREAM> using namespace s

一起talk C栗子吧(第三十四回:C語言實例--巧溢出計算最值)

gcc 空間 代碼 讓我 計算 max value 其他 存儲 點擊 各位看官們。大家好,上一回中咱們說的是巧用移位的樣例,這一回咱們說的樣例是:巧用溢出計算最值。 閑話休提,言歸正轉。讓我們一起talk C栗子吧! 大家都知

LINUX下C語言編程調其他函數、鏈接頭文件以及庫文件

blog head.s 鏈接 color pre () 如果 編譯 聲明 LINUX下C語言編程經常需要鏈接其他函數,而其他函數一般都放在另外.c文件中,或者打包放在一個庫文件裏面,我需要在main函數中調用這些函數,主要有如下幾種方法: 1.當需要調用函數的個數比較少時,

C語言寫一個簡單的三子棋,實現玩家電腦的對戰

原始碼: #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <windows.h> #include <time.h> /* 用 C 寫一個三子棋 */ //邏輯: //1. 畫

C語言】向建立的 d:\\demo.txt 檔案中追加一個字串。

#include<stdio.h> int main() { FILE *fp; char str[102] = { 0 }, strTemp[100]; if ((fp = fopen("D:\\demo.txt", "at+")) == NULL) {

關於C語言函數調的學習【待解決】

png tdi else 分享 運行 函數調用 lse 編寫 div 在學習關於函數調用時,仿照最大函數的調用,編寫了一個求最小值的程序,代碼如下: 1 #include<stdio.h> 2 3 int min(int x,int y); 4 in

C語言如何使用print語句 %d%c%s

C語言如何使用print語句 匿名使用者 2017-10-28 提問 最佳答案 本回答由提問者推薦 匿名使用者1級 2017-10-28 回答 C語言裡printf函式格式控制符的完整格式 printf的格式控制的完整格式: % - 0 m.n l或h

資料結構、演算法應用:C++語言描述》電子書下載 -(百度網盤 高清版PDF格式

    作者:薩尼 (Sartaj Sahni) 出版日期:2010 年 1 月 出版社:機械工業出版社 頁數:542 ISBN:978-7-777-07645-2 檔案格式:PDF 檔案大小:16.48 MB   &n

資料結構 C++ 語言描述》電子書下載 -(百度網盤 高清版PDF格式

      作者:William Ford,William Topp 【譯者】 劉衛東 沉官林 出版日期:1999-9-1 出版社:清華出版社 頁數:708 ISBN:7-302-03160-6 檔案格式:PDF 檔案

JNI之C語言多級指標剖析

一級指標 假設在記憶體中定義一個一級指標。 #include <stdio.h> #include <stdlib.h> main() { int i

C語言進階剖析 05 變數屬性

C語言的變數屬性 C語言中的變數可以有自己的屬性 在定義變數的時候可以加上"屬性"關鍵字 "屬性"關鍵字指明變數的特殊意義 語法: property type var_name; 示例: void code() { auto char i

C語言進階剖析 04 型別轉換

型別之間的轉換 C語言中的數型別可以進行轉換     ○ 強制型別轉換     ○ 隱世型別轉換 void code_1() { long l = 800; int i

C語言進階剖析 03 浮點數的祕密

記憶體中的浮點數 浮點數在記憶體的儲存方式為:符號位,指數,尾數 ○ float 與 double 型別的資料在計算機內部的表示方法是相同的,但是由於所佔儲存空間的不同,其能夠表示的資料範圍和精度不同。 浮點數儲存示例 浮點數的轉換 &nbs