c语言打印字符的函数参数,C语言格式化打印函数vsnprintf()的实现
Linux內(nèi)核的格式化打印函數(shù)是printk(),它與printf()函數(shù)是類似的,都是根據(jù)格式字符串把可變參數(shù)列表轉(zhuǎn)化成字符序列,然后輸出到控制臺。
printf()是打印到標(biāo)準(zhǔn)輸出stdout。
printk()是打印到控制臺終端。在使用串口線連接嵌入式硬件時,就是打印到電腦的串口終端軟件,例如minicom。
轉(zhuǎn)化可變參數(shù)列表這一步,這兩個函數(shù)是一樣的,都是調(diào)用vsnprintf()函數(shù)。
區(qū)別是內(nèi)核沒法調(diào)用C庫,只能另外寫一個簡單的實現(xiàn)。
vsnprintf()的實現(xiàn),依靠的是C語言處理可變參數(shù)的類型valist,以及使用它的三個宏:vastart,vaarg,vaend。
它們都定義在頭文件里。
我在電腦上調(diào)試時,直接把siskavalist定義為了C庫的valist,如上圖。
5-10行注釋掉的部分,是32位C語言的valist定義。
snprintf的代碼就這么幾行,使用vastart獲取參數(shù)列表的開頭,然后調(diào)用vsnprintf()打印出來,最后使用vaend。
對格式串的解析在vsnprintf()里,帶n的printf系列函數(shù)可以標(biāo)示緩沖區(qū)的大小,避免字符串溢出。
vsnprintf()的實現(xiàn):
buf,緩沖區(qū)的地址。
size,緩沖區(qū)的大小。
fmt,格式串。
ap,可變參數(shù)列表,開始時指向它的第1個元素。
先把字符的計數(shù)設(shè)置為0,size -1是為了給末尾的'\0'留一個位置,然后遍歷格式串fmt。
130-133,不是%則直接打印到緩沖區(qū)。
135-139,是%則查看下一個,如果也是%則打印到緩沖區(qū),所以%%會打印%。
141-145,查看是否是十六進制的前綴。
147-151,查看是否是長整型的前綴。
153開始的switch語句是對格式參數(shù)的解析:
154,c表示打印1個字符,它是按照int存儲在參數(shù)里的,所以vaarg的類型選int。
157-162,根據(jù)是否有前綴選擇普通整型或長整型,有符號的。
163-168,同上,無符號的。
169-181,十六進制的整數(shù),根據(jù)格式參數(shù)選擇是否打印0x前綴,是否長整型。
183,p表示打印指針,其中空指針會打印null。
185-188,浮點數(shù),全按double處理。
190,字符串,它的內(nèi)容也是一個'\0'結(jié)尾的char*字符列表。
197,移動到格式串的下一個字符,繼續(xù)判斷while條件。
這時無論格式串到了末尾'\0',還是緩沖區(qū)只剩了最后1個'\0'的空間,都會退出while循環(huán),避免緩沖區(qū)越界。
200行,填充結(jié)尾的'\0',返回轉(zhuǎn)化的字符總數(shù)。
siskaulong2a()函數(shù),是把無符號長整型轉(zhuǎn)換為字符串的函數(shù),普通的整型也用它轉(zhuǎn)換,編譯器會自動把unsigned int類型升級到unsigned long。
打印字符會改變當(dāng)前緩沖區(qū)的字符計數(shù),所以參數(shù)傳了int* pn,即計數(shù)的指針。它既是輸入?yún)?shù),也是輸出參數(shù)。
num %10先獲取個位數(shù),然后 num /10去掉個位數(shù),下一次就是獲取十位數(shù),以此類推,直到為0。具體的字符要加上'0'。
這么打印出來的數(shù)字字符串是反著的,低位先被打印,所以19-23行的while再把它正過來。我們在第6行提前記錄了這串字符的起始位置。
siskalong2a(),有符號的打印除了負數(shù)時要先打印1個負號之外,其他的與無符號的一樣。
siskadouble2a(),浮點數(shù)都是有符號的,負數(shù)也要先打印1個負號,然后先取整數(shù)部分,再取小數(shù)部分,把它們都當(dāng)整數(shù)打印,中間打印小數(shù)點。
小數(shù)部分這里用了6位有效數(shù)字。
siskahex2a(),十六進制的都按無符號處理,除了從10的余數(shù)變成16的余數(shù)之外,與unsigned long的區(qū)別只有67行,即大于9的從'a'開始顯示,9以內(nèi)的加上'0'顯示。
x -10+ 'a',就是10-15要顯示的字符,10對應(yīng)'a',15對應(yīng)'f'。
如果帶前綴打印十六進制,就先打印0x,占2個字符的空間。
siskap2a(),指針都帶0x前綴,按十六進制打印,空指針顯示null。
siskastr2a(),字符串按原樣打印。
main()函數(shù),和測試結(jié)果。
下圖第2張是緩沖區(qū)不足時的打印,第1張是緩沖區(qū)1024字節(jié)的打印。
Linux使用bochs模擬BIOS讀磁盤
先調(diào)用這個函數(shù)把數(shù)據(jù)轉(zhuǎn)化到緩沖區(qū)里,然后通過串口線打印出來,就是printk()。
如果通過標(biāo)準(zhǔn)輸出stdout打印出來,就是printf()。
如果通過FILE* fp 文件句柄打印出來,就是fprintf()。
還可以繼續(xù)添加格式字符,讓它支持更多的數(shù)據(jù)類型。
但在linux內(nèi)核里,實際上連浮點數(shù)都盡量不用,支持有符號和無符號的整數(shù)以及字符串,基本就夠用了。
想了解更多精彩內(nèi)容,快來關(guān)注閑聊代碼
PS:在32位的堆棧傳參模式下,格式串const char* fmt后面就是參數(shù)列表,所以只要取格式串的地址&fmt,加上4字節(jié)就是下一個參數(shù)的地址,然后根據(jù)格式串里%之后的類型字符依次打印就行。
32位是按4字節(jié)對齊,char、short這種不到4字節(jié)的類型也是轉(zhuǎn)化為4字節(jié)壓棧,double、long long這種按8字節(jié)壓棧。
64位是用寄存器傳前6個參數(shù),多于6個的按堆棧傳參,而且還是整數(shù)與浮點數(shù)分開傳,整數(shù)使用rdi、rsi、rdx、rcx、r8、r9,浮點數(shù)使用xmm0、xmm1、xmm2,一直到xmm7。
如果參數(shù)是printf("%d,%f\n",1,2.71)這樣,rdi是格式串,rsi是整數(shù)1,xmm0是浮點數(shù)2.71。
如果自己實現(xiàn)vastart,vaarg的話,需要讓printf()函數(shù)先調(diào)用自己實現(xiàn)的printf(),這樣才能自己控制寄存器參數(shù)的存放順序,然后在printf()里在調(diào)用vsnprintf()。
否則,只能依賴gcc提供的valist,vastart,vaarg,vaend,因為寄存器參數(shù)在這種情況下怎么保存,是編譯器的權(quán)限范圍。
而寄存器參數(shù)的保存方式,則關(guān)系到valist的實現(xiàn)。
總結(jié)
以上是生活随笔為你收集整理的c语言打印字符的函数参数,C语言格式化打印函数vsnprintf()的实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html中获取modelandview中
- 下一篇: mybatis 取查询值_MyBatis