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