自從你進入程序員的世界,就開始照著書本編寫著各種helloworld,大筆一揮:
printf("Hello World!\n");
於是控制台神奇地出現了一行字符串,計算機一句溫馨的問候將多少年輕的騷年們引入了這個比58同城還神奇的世界......
<stdio.h>
main()
{
a = 0.5;
printf("float a is %f\n",a);
0;
}
View Code
於是,我們知道了printf中關鍵的問題就是傳入多個參數,以及如何將信息解析並輸出。而參數的傳遞主要就是由幾個宏完成的:va_list、va_start、va_end,這三個宏的定義在stdarg.h中又被替換了,而真正的定義則是在vadefs.h中:
va_list其實就是char *,唬人用的;va_start(ap, fmt)定位第一個參數的地址,va_end(ap)將ap指針指向空,防止野指針問題。接下來我們可以看到printf函數的實質就是try塊中的三個函數,接著我們跟進去看看到底怎麼回事。
第二步:接著程序指針又跳到了_stfbuf.c文件裡,去執行_stbuf(stdout)函數了。
這個函數主要就是初始化緩沖區,為後面轉化和打印做准備,緩沖區大小是4096個字節,若成功執行返回值為1。
第三步:接下來到文件output.c中的_output_l函數中:
這個函數算是核心吧,首先會設置一大堆的標志值,比如浮點精度,前後綴添0的位數什麼的,具體每個是干什麼的我也說不全。還是來看下面這個過程吧,1068行的while循環就開始解析字符串了,此時format的值就是“float a is %f\n”,一個字符一個字符地取放入ch中,然後下面去檢查類型,比如此時ch的值就是f,是一個普通的字符,所以狀態state的值就設置為ST_NORMAL,去執行下面的switch語句。
switch語句裡,有很多狀態分支,普通字符ST_NORMAL、百分號ST_PERCENT(這個對於printf來說就是比較特殊的)、ST_FLAG(符號)等等。對於普通字符,直接就寫到緩沖區裡去了,每寫一個字符,format指針就向後移一位。當解析到%號時,就執行ST_PERCENT的分支。
之後讀取後一個字符,發現是f則再找到ST_TYPE的分支,執行數據的提取工作。argptr就是指向浮點數a地址的指針。按double類型提取數據並保存到tmp裡。
第四步:當數據提取出來後,執行轉換函數:
當數據保存到tmp中,就可以執行下列這個函數去將數字轉換為字符串並存放到buf裡了printf的關鍵就是如何解析(或者說提取)參數,最終將參數全部轉換為字符串放到一個buffer裡輸出到屏幕上。浮點數轉換後存放在char * 型的text.sz裡,有興趣可以深入這個convert函數裡看看怎麼轉換的~(對於浮點好像有個名叫DTOA函數,記不太清了)
在這些工作完成後output_l函數就返回了,返回的是打印的字符數:20。(你可以數一下下面控制台的輸出字符數,最後包括了換行符)
第五步:將字符串寫到標准輸出流:
返回到printf.c中,執行_ftbuf函數,其中再進入flush函數,在執行到下面指針指向的位置時,控制台才真正將所要打印的字符串打印出來,我們的任務這時才算完成了。
好了,再來說說我為什麼要寫這篇關於printf的文章,是因為在最近的項目裡,碰到了跨平台printf的實現問題,浮點數打印不出來,查了很多資料都沒有什麼相關的,網上眾說紛纭。於是我就到stackoverflow上發了個問,但是卻是一件很不愉快的事情。看看截圖吧,首先是我提的原問題:
接下來就好玩了~:
原帖地址:http://stackoverflow.com/questions/18746570/printf-cannot-print-float-double-correctly-on-the-screen
我確實一開始忘記打參數a,b,c了,但是這不影響問題本身,很明顯最初回答的人根本沒有經過思考,說我連printf的參數怎麼寫都不知道就來問,更有甚者把發問規則給我貼了出來,叫我不要發垃圾問題,還說我亂歸結原因,我只能無奈呵呵了~。那麼我想問的是:首先別人提的問題有你想象的那麼二麼?其次,如果別人真的二,按沒寫參數的代碼區編譯執行會出現所描述的現象麼?在沒有認真看別人問題和現象描述的情況下,秀下限的來了~ 而在我將問題修改過後(僅僅是加上了printf中的a、b、c而已),並且一一回復了留言後(我無一例外的以sorry開頭),前面趕著來秀下限的幾卻個沒有一個能回答上來,甚至連自己的看法都提不出(我能說什麼呢?呵呵!)。
那麼他們為什麼回答不出來呢?因為只有真正做過嵌入式交叉開發的人才會知道這個問題的可能原因,在移植的工作中經常會碰到printf的相關問題,不光是浮點數打印不出來。好吧,看看真正懂的人是怎麼說的吧,有兩個回答都是可能的原因(本來還有人提的一個答案,被我屏蔽掉了,是來秀下限的~),群眾的眼睛是雪亮的,從我在項目中調試的結果看,應該是下面第一個原因,任務棧的字節對齊問題,導致浮點傳參錯誤;第二個回答也是一種可能,但我們在編譯時確實沒有加下面的宏。果斷vote之~:
在C Interfaces and Implementations裡也講解了一種printf的實現,對標准C庫的printf有一定的改進,代碼是要講可重用性和健壯性的,有興趣可以看看。
好了,扪心自問一下,你真的懂printf麼?