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