創(chuàng)衛(wèi)網(wǎng)站 建設 方案青島seo經(jīng)理
[383頁]
1、 這一小節(jié)可以不看代碼如何實現(xiàn),因為標準的C庫函數(shù);
2、 等自己看完的這本書,有興趣過來研究研究也是可以的。
8-11 vsprintf.c程序
8-11-1 功能描述
該程序主要包括vsprintf(),用于對參數(shù)產(chǎn)生格式化的輸出。由于該函數(shù)是C函數(shù)庫中
的標志函數(shù),基本沒有設計內(nèi)核工作原理方面的內(nèi)容,因此可以跳過。直接閱讀代碼后對該函數(shù)
的使用說明。vsprintf()函數(shù)的使用方法參照C庫函數(shù)手冊。
8-11-2 代碼注釋
代碼在后面!!
8-11-3 vsprintf()的格式字符串
int vsprintf(char *buf, const char *fmt, va_list args)
vsprintf()函數(shù)是printf()系列函數(shù)之一。這些函數(shù)都產(chǎn)生格式化的輸出:接受確定輸出格式的格式字符串fmt,用格式字符串對個數(shù)變化的參數(shù)進行格式化,產(chǎn)生格式化的輸出。
printf直接把輸出送到標準句柄stdout。
cprintf把輸出送到控制臺。
fprintf把輸出送到文件句柄。
printf前帶’v’字符的(例如vfprintf)表示參數(shù)是從va_arg數(shù)組的va_list args中接受。
printf前帶’s’字符則表示把輸出送到以null結(jié)尾的字符串Buf中(此時用戶應確保buf有足夠的空間存放字符串)。
下面詳細說明格式字符串的使用方法。
1、 格式字符串
printf系列函數(shù)中的格式字符串用于控制函數(shù)轉(zhuǎn)換方式、格式化和輸出其參數(shù)。對于每個格式,
必須有對應的參數(shù),參數(shù)過多將被忽略。格式字符串中含有兩類成分,
一種是被直接復制到輸出中的簡單字符串;另一種是用于對對應參數(shù)進行格式化的轉(zhuǎn)換指示字符串。
2、格式指示字符串
格式指示串的形式如下:
%[flags][width][.prec][|h|l|L][type]
每一個轉(zhuǎn)換指示串均需要以百分號(%)開始。其中:
[flags]是可選擇的標志字符序列。
[width]是可選擇的寬度指示符。
[.prec]是可選擇的精度(precision)指示符。
[|h|l|L]是可選擇的輸入長度修飾符。
[type]是轉(zhuǎn)換類型字符(或稱為轉(zhuǎn)換指示符)。
8-11-4 與當前版本的區(qū)別
由于該文件也屬于庫函數(shù),所以從1.2版本內(nèi)核開始就直接使用庫中的函數(shù)了。即刪除了該文件。
/** linux/kernel/vsprintf.c** (C) 1991 Linus Torvalds*//* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */
/** Wirzenius wrote this portably, Torvalds fucked it up :-)*/
// Lars Wirzenius是Linus的好友,在Helsinki大學時曾同處一間辦公室。在1991年夏季開發(fā)Linux
// 時,Linus當時對C語言還不是很熟悉,還不會使用可變參數(shù)列表函數(shù)功能。因此Lars Wirzenius
// 就為他編寫了這段用于內(nèi)核顯示信息的代碼。他后來(1998年)承認在這段代碼中有一個bug,直到
// 1994年才有人發(fā)現(xiàn),并予以糾正。這個bug是在使用*作為輸出域?qū)挾葧r,忘記遞增指針跳過這個星
// 號了。在本代碼中這個bug還仍然存在(130行)。 他的個人主頁是http://liw.iki.fi/liw/
#include <stdarg.h> // 標準參數(shù)頭文件。以宏的形式定義變量參數(shù)列表。主要說明了-個// 類型(va_list)和三個宏(va_start, va_arg和va_end),用于// vsprintf、vprintf、vfprintf函數(shù)。
#include <string.h>// 字符串頭文件。主要定義了一些有關(guān)字符串操作的嵌入函數(shù)。/* 我們使用下面的定義,這樣我們就可以不使用ctype庫了 */
#define is_digit(c) ((c) >= '0' && (c) <= '9')// 判斷字符c是否為數(shù)字字符。
// 該函數(shù)將字符數(shù)字串轉(zhuǎn)換成整數(shù)。輸入是數(shù)字串指針的指針,返回是結(jié)果數(shù)值。另外指針將前移。
static int skip_atoi(const char **s)
{int i=0;while (is_digit(**s))i = i*10 + *((*s)++) - '0';return i;
}
// 這里定義轉(zhuǎn)換類型的各種符號常數(shù)。
#define ZEROPAD 1 /* 填充零 */
#define SIGN 2 /* 無符號/符號長整數(shù) */
#define PLUS 4 /* 顯示加 */
#define SPACE 8 /* 如是加,則置空格 */
#define LEFT 16 /* 左調(diào)整 */
#define SPECIAL 32 /* 0x */
#define SMALL 64 /* 使用小寫字母 */// 除操作。輸入:n為被除數(shù),base為除數(shù);結(jié)果:n為商,函數(shù)返回值為余數(shù)。
// 參見4.5.3節(jié)有關(guān)嵌入?yún)R編的信息。
#define do_div(n,base) ({ \
int __res; \
__asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base)); \
__res; })// 將整數(shù)轉(zhuǎn)換為指定進制的字符串。
// 輸入:num-整數(shù);base-進制;size-字符串長度;precision-數(shù)字長度(精度);type-類型選項。
// 輸出:數(shù)字轉(zhuǎn)換成字符串后指向該字符串末端后面的指針。
static char * number(char * str, int num, int base, int size, int precision,int type)
{char c,sign,tmp[36];const char *digits="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";int i;// 如果類型type指出用小寫字母,則定義小寫字母集。// 如果類型指出要左調(diào)整(靠左邊界),則屏蔽類型中的填零標志。// 如果進制基數(shù)小于2或大于36,則退出處理,也即本程序只能處理基數(shù)在2-32之間的數(shù)。if (type&SMALL) digits="0123456789abcdefghijklmnopqrstuvwxyz";if (type&LEFT) type &= ~ZEROPAD;if (base<2 || base>36)return 0;// 如果類型指出要填零,則置字符變量c='0',否則c等于空格字符。// 如果類型指出是帶符號數(shù)并且數(shù)值num小于0,則置符號變量sign=負號,并使num取絕對值。// 否則如果類型指出是加號,則置sign=加號,否則若類型帶空格標志則sign=空格,否則置0。 c = (type & ZEROPAD) ? '0' : ' ' ;if (type&SIGN && num<0) {sign='-';num = -num;} elsesign=(type&PLUS) ? '+' : ((type&SPACE) ? ' ' : 0);// 若帶符號,則寬度值減1。若類型指出是特殊轉(zhuǎn)換,則對于十六進制寬度再減少2位(用于0x),// 對于八進制寬度減1(用于八進制轉(zhuǎn)換結(jié)果前放一個零)。if (sign) size--;if (type&SPECIAL)if (base==16) size -= 2;else if (base==8) size--;// 如果數(shù)值num為0,則臨時字符串='0';否則根據(jù)給定的基數(shù)將數(shù)值num轉(zhuǎn)換成字符形式。i=0;if (num==0)tmp[i++]='0';else while (num!=0)tmp[i++]=digits[do_div(num,base)];// 若數(shù)值字符個數(shù)大于精度值,則精度值擴展為數(shù)字個數(shù)值。// 寬度值size減去用于存放數(shù)值字符的個數(shù)。if (i>precision) precision=i;size -= precision;// 從這里真正開始形成所需要的轉(zhuǎn)換結(jié)果,并暫時放在字符串str中。// 若類型中沒有填零(ZEROPAD)和左靠齊(左調(diào)整)標志,則在str中首先// 填放剩余寬度值指出的空格數(shù)。若需帶符號位,則存入符號。if (!(type&(ZEROPAD+LEFT)))while(size-->0)*str++ = ' ';if (sign)*str++ = sign;// 若類型指出是特殊轉(zhuǎn)換,則對于八進制轉(zhuǎn)換結(jié)果頭一位放置一個'0';而對于十六進制則存放'0x'。if (type&SPECIAL)if (base==8)*str++ = '0';else if (base==16) {*str++ = '0';*str++ = digits[33];}// 若類型中沒有左調(diào)整(左靠齊)標志,則在剩余寬度中存放c字符('0'或空格),見51行。 if (!(type&LEFT))while(size-->0)*str++ = c;// 此時i存有數(shù)值num的數(shù)字個數(shù)。若數(shù)字個數(shù)小于精度值,則str中放入(精度值-i)個'0'。 while(i<precision--)*str++ = '0';// 將數(shù)值轉(zhuǎn)換好的數(shù)字字符填入str中。共i個。 while(i-->0)*str++ = tmp[i];// 若寬度值仍大于零,則表示類型標志中有左靠齊標志。則在剩余寬度中放入空格。 while(size-->0)*str++ = ' ';return str;// 返回轉(zhuǎn)換好的指向字符串末端后的指針。
}
// 下面函數(shù)是送格式化輸出到字符串中。
// 為了能在內(nèi)核中使用格式化的輸出,Linus在內(nèi)核實現(xiàn)了該C標準函數(shù)。
// 其中參數(shù)fmt是格式字符串;args是個數(shù)變化的值;buf是輸出字符串緩沖區(qū)。
// 請參見本代碼列表后的有關(guān)格式轉(zhuǎn)換字符的介紹。
int vsprintf(char *buf, const char *fmt, va_list args)
{int len;int i;char * str;// 用于存放轉(zhuǎn)換過程中的字符串。char *s;int *ip;int flags; /* number()函數(shù)使用的標志 */int field_width; /* 輸出字段寬度*/int precision; /* min. 整數(shù)數(shù)字個數(shù);max. 字符串中字符個數(shù) */int qualifier; /* 'h', 'l',或'L'用于整數(shù)字段 */// 首先將字符指針指向buf,然后掃描格式字符串,對各個格式轉(zhuǎn)換指示進行相應的處理。for (str=buf ; *fmt ; ++fmt) {// 格式轉(zhuǎn)換指示字符串均以'%'開始,這里從fmt格式字符串中掃描'%',尋找格式轉(zhuǎn)換字符串的開始。// 不是格式指示的一般字符均被依次存入str。if (*fmt != '%') {*str++ = *fmt;continue;}
// 下面取得格式指示字符串中的標志域,并將標志常量放入flags變量中。 /* process flags */flags = 0;repeat:++fmt; /* this also skips first '%' */switch (*fmt) {case '-': flags |= LEFT; goto repeat;// 左靠齊調(diào)整。case '+': flags |= PLUS; goto repeat;// 放加號。case ' ': flags |= SPACE; goto repeat;// 放空格。case '#': flags |= SPECIAL; goto repeat;// 是特殊轉(zhuǎn)換。case '0': flags |= ZEROPAD; goto repeat;// 要填零(即'0')。}// 取當前參數(shù)字段寬度域值,放入field_width變量中。如果寬度域中是數(shù)值則直接取其為寬度值。// 如果寬度域中是字符'*',表示下一個參數(shù)指定寬度。因此調(diào)用va_arg取寬度值。若此時寬度值// 小于0,則該負數(shù)表示其帶有標志域'-'標志(左靠齊),因此還需在標志變量中添入該標志,并// 將字段寬度值取為其絕對值。 /* get field width */field_width = -1;if (is_digit(*fmt))field_width = skip_atoi(&fmt);else if (*fmt == '*') {/* it's the next argument */ // 這里有個bug,應插入++fmt;field_width = va_arg(args, int);if (field_width < 0) {field_width = -field_width;flags |= LEFT;}}// 下面這段代碼,取格式轉(zhuǎn)換串的精度域,并放入precision變量中。精度域開始的標志是'.'。// 其處理過程與上面寬度域的類似。如果精度域中是數(shù)值則直接取其為精度值。如果精度域中是// 字符'*',表示下一個參數(shù)指定精度。因此調(diào)用va_arg取精度值。若此時寬度值小于0,則將// 字段精度值取為0。/* get the precision */precision = -1;if (*fmt == '.') {++fmt; if (is_digit(*fmt))precision = skip_atoi(&fmt);else if (*fmt == '*') {/* it's the next argument */ // 同上這里也應插入++fmt;precision = va_arg(args, int);}if (precision < 0)precision = 0;}
// 下面這段代碼分析長度修飾符,并將其存入qualifer變量。(h,l,L的含義參見列表后的說明)。/* get the conversion qualifier */qualifier = -1;if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {qualifier = *fmt;++fmt;}
// 下面分析轉(zhuǎn)換指示符。switch (*fmt) {
// 如果轉(zhuǎn)換指示符是'c',則表示對應參數(shù)應是字符。此時如果標志域表明不是左靠齊,則該字段前面
// 放入'寬度域值-1'個空格字符,然后再放入?yún)?shù)字符。如果寬度域還大于0,則表示為左靠齊,則在
// 參數(shù)字符后面添加'寬度值-1'個空格字符。case 'c':if (!(flags & LEFT))while (--field_width > 0)*str++ = ' ';*str++ = (unsigned char) va_arg(args, int);while (--field_width > 0)*str++ = ' ';break;// 如果轉(zhuǎn)換指示符是's',則表示對應參數(shù)是字符串。首先取參數(shù)字符串的長度,若其超過了精度域值,// 則擴展精度域=字符串長度。此時如果標志域表明不是左靠齊,則該字段前放入'寬度值-字符串長度'// 個空格字符。然后再放入?yún)?shù)字符串。如果寬度域還大于0,則表示為左靠齊,則在參數(shù)字符串后面// 添加'寬度值-字符串長度'個空格字符。case 's':s = va_arg(args, char *);len = strlen(s);if (precision < 0)precision = len;else if (len > precision)len = precision;if (!(flags & LEFT))while (len < field_width--)*str++ = ' ';for (i = 0; i < len; ++i)*str++ = *s++;while (len < field_width--)*str++ = ' ';break;
// 如果格式轉(zhuǎn)換符是'o',表示需將對應的參數(shù)轉(zhuǎn)換成八進制數(shù)的字符串。調(diào)用number()函數(shù)處理。case 'o':str = number(str, va_arg(args, unsigned long), 8,field_width, precision, flags);break;
// 如果格式轉(zhuǎn)換符是'p',表示對應參數(shù)是一個指針類型。此時若該參數(shù)沒有設置寬度域,則默認寬度
// 為8,并且需要添零。然后調(diào)用number()函數(shù)進行處理。case 'p':if (field_width == -1) {field_width = 8;flags |= ZEROPAD;}str = number(str,(unsigned long) va_arg(args, void *), 16,field_width, precision, flags);break;
// 若格式轉(zhuǎn)換指示是'x'或'X',則表示對應參數(shù)需要打印成十六進制數(shù)輸出。'x'表示用小寫字母表示。case 'x':flags |= SMALL;case 'X':str = number(str, va_arg(args, unsigned long), 16,field_width, precision, flags);break;// 如果格式轉(zhuǎn)換字符是'd','i'或'u',則表示對應參數(shù)是整數(shù),'d', 'i'代表符號整數(shù),因此需要加上// 帶符號標志。'u'代表無符號整數(shù)。case 'd':case 'i':flags |= SIGN;case 'u':str = number(str, va_arg(args, unsigned long), 10,field_width, precision, flags);break;// 若格式轉(zhuǎn)換指示符是'n',則表示要把到目前為止轉(zhuǎn)換輸出字符數(shù)保存到對應參數(shù)指針指定的位置中。// 首先利用va_arg()取得該參數(shù)指針,然后將已經(jīng)轉(zhuǎn)換好的字符數(shù)存入該指針所指的位置。case 'n':ip = va_arg(args, int *);*ip = (str - buf);break;// 若格式轉(zhuǎn)換符不是'%',則表示格式字符串有錯,直接將一個'%'寫入輸出串中。// 如果格式轉(zhuǎn)換符的位置處還有字符,則也直接將該字符寫入輸出串中,并返回到107行繼續(xù)處理// 格式字符串。否則表示已經(jīng)處理到格式字符串的結(jié)尾處,則退出循環(huán)。default:if (*fmt != '%')*str++ = '%';if (*fmt)*str++ = *fmt;else--fmt;break;}}*str = '\0';// 最后在轉(zhuǎn)換好的字符串結(jié)尾處添上null。return str-buf;// 返回轉(zhuǎn)換好的字符串長度值。
}