c++ printf怎么用_【经典】把脉printf中的C进阶技巧
1、聊一聊
今天跟大家分享一首坤坤同學的新歌,個人覺得旋律有一種花花公子的味道,還是比較帶感的,大家感興趣可以欣賞下!好了,今天為大家帶來一篇printf剖析的文章,該函數應該是大家在初學階段用得非常頻繁的一個函數,進入嵌入式的小伙伴也是經常將其重定位進行系統相關調試信息的打印,同樣也有很多大佬為了增加程序運行效率直接將其進行了改寫來滿足需求。所以今天作者就帶大家看看printf里面究竟是怎么處理的?為什么能夠輸入可變參數?到底支持哪些輸出輸出?等等。2、想看看C庫源碼怎么辦?
? ? 在嵌入式中一般談到C Library大家都會想到glibc,glibc是GUN旗下的一個C標準庫。那么libc又是什么呢?對于這個名詞的定義有點歧義,有些人把所有的C標準庫都統稱為libc,而有部分人認為libc是最開始linux下的標準庫。
????所以說C標準庫也是多種多樣的,不同平臺都有所不同,比如嵌入式中非常小型的uClibc等等。
????大家如果有對C庫感興趣的可以去簡單看一些源碼,里面的寶藏也是特別的豐富。下面作者提供glic和uClibc的網站,大家可以到網站下載對應的源碼進行研究。
glibc官方網站 : http://www.gnu.org/software/libc/
uClibc官方網站 :?https://uclibc-ng.org/
?? ?由于uClibc相對glibc來說小得多,所以在嵌入式中也是經常使用到,作者也是特意下載了源碼并解壓出來了,證明訪問路徑是OK的:
?? ?并用sourceInsight打開找到了printf的具體實現:
?? ?由于glibc和uClibc中的printf實現相對嵌套比較多,不便于直接分析,后面作者會選擇相對比較有條理的printf函數實現進行分析講解,設計實現上都是大同小異,如果大家感興趣也可以下載源碼進行閱讀。
3、函數參數入棧問題
????C語言函數參數一定會入棧嗎?入棧一定是從右向左嗎?
????其實這是一個與平臺和處理器相關的問題,所以需要具體情況具體分析,首先大家要明確函數參數和定義的局部變量大部分都是存在堆棧中(不過也可以通過借助寄存器來傳遞,比如說之前剖析register關鍵字文章中把局部變量放到寄存器中提高效率),使用完畢以后通過堆棧指針的移動進行自動的釋放。
????對于X86-32bit平臺一般都是從右向左參數入棧,而對于X86-64bit為了提高程序運行效率,會把前面的部分參數通過對應的寄存器進行傳遞,如果有更多的參數就通過壓棧進行處理,所以需要根據具體的平臺和編譯器進行分析。那么為什么作者這里首先提到入棧順序呢?因為printf需要實現可變參數,那么肯定是需要有約定的傳參數的規則,該約定的規則就決定了函數內部如何獲得對應參數。
4、可變參數的實現
????對于大部分小伙伴在平時的開發中基本上都是使用固定的參數類型,不過對于類似于printf這種用戶接口使用型函數,實現可變參數就顯得更加具有靈活性。學習過C++的小伙伴應該有種感覺,可變參數有點類似于函數重載,不過C的可變參數必須包含一個參數。下面作者簡單的實現一個可變參數函數使用demo供大家參考。
參考demo: 1#include 2#include 3 4/*********************************** 5?*?Fuction:?sCalSum 6?* Author :(公眾號:最后一個bug) 7?**********************************/ 8int?sCalSum(int?Num,...) 9{10????//定義獲取參數列表結構體11????va_list?ap;12????int?sum?=?0;13????int?i?=?0?;14????//定位起始變量15????va_start(ap,?Num);1617????for(i?=?0?;i?18????{19????????//根據參數類型進行索引20????????sum+=?va_arg(ap,int);21????}22????//結束變量獲取,并釋放資源23????va_end(ap);24????return?sum;25}262728int?main(void)29{30????printf("%d?+?%d?=?%d\n",1,2,sCalSum(2,1,2));31????printf("%d?+?%d?+?%d?=?%d\n",1,2,4,sCalSum(3,1,2,4));32????printf("%d?+?%d?+?%d?+?%d?=?%d\n",1,2,4,8,sCalSum(4,1,2,4,8));33????return?0;34?}最后輸出結果:5、printf源碼分析
????前面的兩個知識點都是為下面printf源碼分析鋪路的,浮點在處理器中運算是比較耗時間的,同時占用的資源也是非常多的,所以很多集成開發環境或者編譯鏈接工具都會為標準庫提供精簡版本供大家選擇。
????特別是對于使用單片機的小伙伴調用庫相關的函數,如果精簡版本能夠滿足需求,就盡量使用精簡版本,如果覺得精簡版本還是太占用資源,那就自己手動編寫修改吧,所以printf中的浮點處理成為了精簡的一部分,如果在使用過程中發現使用printf打印不了浮點可以查看一下是不是libc中不支持浮點打印等相關功能。(下圖是IAR編譯工具中的相關配置選項)
????為了方便大家學習和理解,所以這里并沒有選用非常復雜的函數實現,而是選用IAR中的精簡版printf跟大家講講思路:(下面的代碼截圖均來自IAR安裝目錄,IAR安裝目錄下還有很多其他寶藏,大家可以參考學習)
具體實現過程:在調用printf函數都會使用到頭文件#include,所以大家搜索該文件即可,然后順著包含關系可以找到其他函數設計實現,所以推薦大家使用SI編輯器閱碼。
下面作者截取了printf函數實現,大家仔細觀察會發現printf竟然還有返回值,估計80%的小伙伴都沒有使用過吧。
從printf函數形式來看來和我們前面實現的可變參數實現并沒有太大的區別,只是說第一個參數變成了指針,這個指針就是平時所指定的參數格式,如"ADCSample:%d",函數內部就是通過解析該字符串獲得后面傳入參數的具體類型等信息,從而進行相應的轉化處理。
vprintf函數會最終調用vfprintf, vfprintf調用vsprintf,如下圖所示
?對于vfprintf函數中vsprintf僅僅只是通過ap參數和pFormat格式進行轉化為pstr,通過pstr把最終的輸出信息通過fputs進行輸出,所以你只需要改寫對應 fputs就可以把最終輸出到對應的終端上(比如串口,LCD屏幕等等),所以玩stm32使用重新位串口輸出也就是同樣的道理了。
下面我們來具體看看vsprintf里面的實現思路,vsprintf會調用vsnprintf,同時通過宏定義限制了最終通過printf的長度。
下面我們截取vsnprintf中重要的兩段來進行分析:
該部分通過%來進行每個參數格式的查找。
通過不同格式對參數列表中的參數進行對應的解析,所以說%的順序也就決定了參數的順序,對于不同參數類型的轉化封裝成了不同的函數,大家有時間可以細細讀讀里面具體的實現代碼,這里就不展開了,細心的小伙伴應該會發現里面并沒有%f的相關處理,因為被精簡了。
總體來看printf實現并不是很復雜,因為C庫中封裝的va_arg宏把參數為我們準備好了,前面我們說了不同的平臺函數參數處理不一樣,所以va_arg搜索參數的實現方法也不盡相同,不過肯定是根據相應的約定進行查找,最簡單的約定就是全部壓入到堆棧中,然后通過堆棧指針根據參數類型一一獲得對應的參數值。下面是IAR中一段該部分的實現,大家欣賞一下就好了。
5、最后小節
??? printf函數的基本實現就跟大家講解到這里,其實很多libc并沒有想象中那么神秘,大家如果在平時使用libc的過程中發現了相關問題完全可以通過閱讀相關源碼進行分析處理,也可以對其源碼進行改寫來滿足自身需求,自所謂"見源碼如見真理"。
????好了,這里是公眾號:“最后一個bug”,一個為大家打造的技術知識提升基地。同時非常感謝各位小伙伴的支持,我們下期精彩見!
推薦好文??點擊藍色字體即可跳轉
?【重磅】【完全解讀】RTOS中的任務是線程?進程?還是協程?
?【漲知識】OS下的內存使用原來這么復雜
?【原理分析】來看看慣性輪自平衡自行車實現原理
?【重磅】剖析MCU的IAP升級軟件設計(設計思路篇)
??【典藏】別怪"浮點數"太坑(C語言版本)
?GUI必備知識之“告別”亂碼(淺顯易懂)
?【典藏】大佬們都在用的結構體進階小技巧
?聽說因為代碼沒"對齊"程序就奔了?(深度剖析)
?【典藏】自制小型GUI界面框架(設計思想篇)
總結
以上是生活随笔為你收集整理的c++ printf怎么用_【经典】把脉printf中的C进阶技巧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 欧盟监管机构推迟对微软收购动视暴雪作出裁
- 下一篇: c语言调用tensorflow模型,C+