printf 原始碼 實現

來源:互聯網
上載者:User

開啟Source Insight來閱讀EduOS的原始碼,我們在stdio.c裡找到了printf的實現代碼.首先看看對printf的定義:

int printf (const char *cntrl_string, ...)

第一個參數cntrl_string是控制字元串,也就是平常我們寫入%d,%f的地方.緊接著後面是一個變長參數.

看看函數頭部的定義:

int pos = 0, cnt_printed_chars = 0, i;
unsigned char* chptr;
va_list ap;

馬上暈!除了ap我們可以馬上判斷出來是用來讀取變長參數的,i用於迴圈變數.其他變數都不知道是怎麼回事.不要著急,我們邊看代碼邊分析.代碼的第一行必然是

va_start (ap, cntrl_string);

用來初始化變長參數.

接下來是一個while迴圈

while (cntrl_string[pos]) {
...
}

結束條件是cntrl_string[pos]為NULL,顯然這個迴圈是用來遍曆整個控制字元串的.自然pos就是當前遍曆到的位置了.進入迴圈首先闖入視線的是

if (cntrl_string[pos] == '%') {
pos++;
...
}

開門見山,上來就當前字元是否辦斷是否%.一猜就知道如果成立pos++馬上取出下一個字元在d,f,l等等之間進行判斷.往下一看,果真不出所料:

switch (cntrl_string[pos]) {
case 'c':
...
case 's':
...
case 'i':
...
case 'd':
...
case 'u':
...

用上switch-case了. 快速探索一下下面的代碼.

首先看看case 'c'的部分

case 'c':
putchar (va_arg (ap, unsigned char));
cnt_printed_chars++;
break;

%c表示僅僅輸出一個字元.因此先通過va_arg進行參數的類型轉換,之後用putchar[1]輸出到螢幕上去.之後是
cnt_printed_chars++,通過這句我們就可以判斷出cnt_printed_chars使用來表示,已經被printf輸出的字元個數的.

再來看看 case 's':
case 's':
chptr = va_arg (ap, unsigned char*);
i = 0;
while (chptr [i]) {
cnt_printed_chars++;
putchar (chptr [i++]);
}
break;
和case 'c',同出一轍.cnt_printed_chars++放在了迴圈內,也證明了剛才提到的他的作用.另外我們也看到了cnptr是用來在處理字串時的位置指標.到此為止,我們清楚的所有變數的用途,前途變得更加光明了.

接下來:
// PartI
case 'i':
case 'd':
cnt_printed_chars += printInt (va_arg (ap, int));
break;
case 'u':
cnt_printed_chars += printUnsignedInt (va_arg (ap, unsigned int));
break;
case 'x':
cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'x');
break;
case 'X':
cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'X');
break;
case 'o':
cnt_printed_chars += printOctal (va_arg (ap, unsigned int));
break;
// Part II
case 'p':
putchar ('0');
putchar ('x');
cnt_printed_chars += 2; /* of '0x' */
cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'x');
break;
case '#':
pos++;
switch (cntrl_string[pos]) {
case 'x':
putchar ('0');
putchar ('x');
cnt_printed_chars += 2; /* of '0x' */
cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'x');
break;
case 'X':
putchar ('0');
putchar ('X');
cnt_printed_chars += 2; /* of '0X' */
cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'X');
break;
case 'o':
putchar ('0');
cnt_printed_chars++;
cnt_printed_chars += printOctal (va_arg (ap, unsigned int));
break;

注意觀察一下,PartII的代碼其實就是比PartI的代碼多一個樣式.在16進位數或八進位前加入0x或是o,等等.因此這裡就只分析一下PartI咯.

其實仔細看看PartI的個條case,也就是把參數分發到了更具體的函數用於顯示,然後以傳回值的形式返回輸出個數.對於這些函數就不具體分析了.我們先來看看一些善後處理:

先看case的default處理.
default:
putchar ((unsigned char) cntrl_string[pos]);
cnt_printed_chars++;
就是直接輸出cntrl_string裡%號後面的未知字元.應該是一種容錯設計處理.

再看看if (cntrl_string[pos] == '%')的else部分

else {
putchar ((unsigned char) cntrl_string[pos]);
cnt_printed_chars++;
pos++;
}

如果不是%開頭的,那麼直接輸出這個字元.

最後函數返回前
va_end (ap);
return cnt_printed_chars;
va_end處理變長參數的善後工作.並返回輸出的字元個數.

在最後我們有必要談談putChar函數以及基本輸出的基礎函數printChar,先來看看putChar

int putchar (int c) {
switch ((unsigned char) c) {
case '\n' :
newLine ();
break;
case '\r' :
carriageReturn ();
break;
case '\f' :
clearScreen ();
break;
case '\t' :
printChar (32); printChar (32); /* 32 = space */
printChar (32); printChar (32);
printChar (32); printChar (32);
printChar (32); printChar (32);
break;
case '\b':
backspace ();
break;
case '\a':
beep ();
break;
default :
printChar ((unsigned char) c);
}
return c;
}

通覽一下,也是switch-case為主體的.主要是用來應對一些特殊字元,如\n,\r,....這裡需要提一下,關於\t的理解.有些人認為\t就是8個space,有些人則認為,螢幕分為10大列(每個大列8個小列總共80列).一個\t就跳到下一個大列輸出.也就是說不管你現在實在螢幕的第1,2,3,4,5,6,7位置輸出字元,只要一個\t都在第8個位置開始輸出. VS.NET中就是用的這種理解.因此如果按照這個理解的話,\t的實現可以這樣

int currentX = ((currentX % 10) + 1) * 8;

然後在currentX位置輸出.

接下來看printChar也就是輸出部分最低層的操作咯

void printChar (const byte ch) {
*(word *)(VIDEO + y * 160 + x * 2) = ch | (fill_color << 8);
x++;
if (x >= WIDTH)
newLine ();
setVideoCursor (y, x);
}

這裡VIDEO表示顯存地址也就是0xB8000.通過 y * 160 + x 螢幕(x,y)座標在顯存中的位置.這裡需要知道,一個字元顯示需要兩個位元組,一個是ASCII碼,第二個是字元屬性代碼也就是顏色代碼.因此才必須 y * 80 * 2 + x = y * 160 + x.那麼ch | (fill_color << 8)也自然就是寫入字元及屬性代碼用的了.每寫一個字元游標位置加1,如果大於螢幕寬度WIDTH就換行.最後通過setVideoCursor設定新的游標位置.完成了整個printChar過程.

轉載自http://hi.baidu.com/yangyangye2008/blog/item/e2f251b5e5e025688bd4b2d7.html

——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

深入printf閱讀:359次   時間:2004-10-07 00:00:00   字型:[大
中 小]

/***
*printf.c - print formatted
*
* Copyright (c) 1985-1997, Microsoft Corporation. All rights reserved.
*
*Purpose:
* defines printf() - print formatted data
*
*******************************************************************************/

#include
#include
#include
#include
#include
#include
#include

/***
*int printf(format, ...) - print formatted data
*
*Purpose:
* Prints formatted data on stdout using the format string to
* format data and getting as many arguments as called for
* Uses temporary buffering to improve efficiency.
* _output does the real work here
*
*Entry:
* char *format - format string to control data format/number of arguments
* followed by list of arguments, number and type controlled by
* format string
*
*Exit:
* returns number of characters printed
*
*Exceptions:
*
*******************************************************************************/

int __cdecl printf (
const char *format,
...
)
/*
* stdout ''PRINT'', ''F''ormatted
*/
{
va_list arglist;
int buffing;
int retval;

va_start(arglist, format);

_ASSERTE(format != NULL);//斷言宏。如果輸出格式字串指標為空白,則在DEBUG版下斷言,報告錯誤。

_lock_str2(1, stdout);

buffing = _stbuf(stdout);//stdout:指定輸出到螢幕

retval = _output(stdout,format,arglist);

_ftbuf(buffing, stdout);

_unlock_str2(1, stdout);

return(retval);
}
以上為printf()的原始碼
1、從含有選擇性參數函數中獲得選擇性參數,以及操作這些參數
typedef char *va_list;
void va_start( va_list arg_ptr, prev_param );
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
假定函數含有一個必選參數和多個選擇性參數,必選參數聲明為普通資料類型,且能通過參數名來獲得該變數的值。選擇性參數通過宏va_start、va_arg和va_end(定義在stdarg.h或varargs.h中)來進行操作,即通過設定指向第一個選擇性參數指標、返回當前參數、在返回參數後重新設定指標來操作所有的選擇性參數。
va_start:為擷取可變數目參數的函數的參數提供一種便捷手段。設定arg_ptr為指向傳給函數參數列表中的第一個選擇性參數的指標,且該參數必須是va_list類型。prev_param是在參數列表中第一個選擇性參數前的必選參數。
va_arg:返回由arg_ptr所指向的參數的值,且自增指向下一個參數的地址。type為當前參數的類型,用來計算該參數的長度,確定下一個參數的起始位置。它可以在函數中應用多次,直到得到函數的所有參數為止,但必須在宏va_start後面調用。
va_end:在擷取所有的參數後,設定指標arg_ptr為NULL。
下面舉例說明:
#include
#include
int average( int first, ... );
void main( void )
{
/* Call with 3 integers (-1 is used as terminator). */
printf( "Average is: %d\n", average( 2, 3, 4, -1 ) );

/* Call with 4 integers. */
printf( "Average is: %d\n", average( 5, 7, 9, 11, -1 ) );

/* Call with just -1 terminator. */
printf( "Average is: %d\n", average( -1 ) );
}

int average( int first, ... )
{
int count = 0, sum = 0, i = first;
va_list marker;

va_start( marker, first ); /* Initialize variable arguments. */
while( i != -1 )
{
sum += i;
count++;
i = va_arg( marker, int);
}
va_end( marker ); /* Reset variable arguments. */
return( sum ? (sum / count) : 0 );
}
傳回值為:
Average is: 3
Average is: 8
Average is: 0
綜上所述,在printf()函數中,可以只輸出一個字串,也可按照一定的形式輸出含有多個選擇性參數的字串資訊。因此,首先就要通過這些宏來擷取所有的選擇性參數。在上面的源碼可以看出printf()中,只使用了宏at_start,將選擇性參數的首地址賦給了arglist。
2、鎖定字串及輸出字串到螢幕
#define _lock_str2(i,s) _lock_file2(i,s)
void __cdecl _lock_file2(int, void *);
#define _unlock_str2(i,s) _unlock_file2(i,s)
void __cdecl _unlock_file2(int, void *);
int __cdecl _stbuf(FILE *);
void __cdecl _ftbuf(int, FILE *);
int __cdecl _output(FILE *, const char *, va_list);
在output函數中,讀取格式字串中的每一個字元,然後對其進行處理,處理方式根據每一個字元所代表的意義來進行,如:一般字元直接利用函數WRITE_CHAR(ch, &charsout);輸出到控制台。
其中的主要部分是對轉換說明符(d,c,s,f)的處理,現在將對其中的部分代碼進行詳細說明,這裡只說明最基本的轉換說明符,對這些須基本的轉換說明符進行修飾的修飾符,程式中單獨進行處理。下面是函數output()(output.c)部分原始碼:
case ST_TYPE:
//表示當前處理的字元的類型為轉換說明符。
...
switch (ch) {
//下面對參數的擷取都是利用宏va_arg( va_list arg_ptr, type );來進行的。
case ''c'': {
//從參數表中擷取單個字元,輸出到緩衝字串中,此時,type=int
buffer[0] = (char) get_int_arg(&argptr); /* get char to print */
text = buffer;
textlen = 1; /* print just a single character */
}
break;

case ''s'': {
//從參數表中擷取字串,輸出到緩衝字串中,此時,type=char*
int i;
char *p; /* temps */
text = get_ptr_arg(&argptr);
...
}
break;

case ''w'': {
//對寬字元進行處理
...
} /* case ''w'' */
break;
...
case ''e'':
case ''f'':
case ''g'': {
//對浮點數進行操作
...
#if !LONGDOUBLE_IS_DOUBLE
/* do the conversion */
if (flags & FL_LONGDOUBLE) {
_cldcvt((LONGDOUBLE*)argptr, text, ch, precision, capexp);
va_arg(argptr, LONGDOUBLE);
//對長雙精確度型進行處理,此時,type=long double
}
else
#endif /* !LONGDOUBLE_IS_DOUBLE */
{

//對雙精確度型進行處理,此時,type=double
_cfltcvt((DOUBLE*)argptr, text, ch, precision, capexp);
va_arg(argptr, DOUBLE);
}
...
break;
//對整型變數處理
case ''d'':
case ''i'':
...
goto COMMON_INT;

case ''u'':
radix = 10;
goto COMMON_INT;

case ''p'':
...
goto COMMON_INT;

case ''o'':
...

註:對於浮點型double和long double,有相應的轉換說明符(%f表示雙精確度型,%lf表示長雙精確度型),而float卻沒有。其中的原因是,在K&RC下,float值用於運算式或用作參數前,會自動轉換成double類型。而ANSI C一般不會自動把float轉換成double。有些程式已假定其中的float參數會被轉換成double,為了保護大量這樣的程式,所有printf()函數的float參數還是被自動轉換成double型。因此,在K&RC或ANSI C下,都無需用特定的轉換說明符來顯示float型。
綜上所述,轉換說明符必須與待列印字元的類型。通常,使用者有種選擇。例如,如要列印一個int類型的值。則只可以使用%d,%x或%o。所有這些說明符都表示要列印一個int類型的值;它們只不過提供了一個數值的幾種不同表示。類似一,可以用%f、%g和%e來表示double類型的值。但如果轉換說明與類型不符,將會出現意想不到的結果。為什麼呢?問題就在於C向函數傳遞資訊的方式。
這個失敗的根本細節與具體實現相關。它決定了系統中的參數以何方式傳遞。函數調用如下:
float n1;
double n2;
long n3;
long n4;
...
printf("%ld,%ld,%ld,%ld",n1,n2,n3,n4);
這個調用告訴電腦,要把變數n1,n2,n3和n4的值交給電腦,它把這些變數放進稱作棧(stack)的記憶體地區中,來完成這一任務。電腦把這些值放進棧中,其根據是變數的類型而不是轉換說明符,比如n1,把8個位元組放入棧中(float被轉換成double),類似地,為n2放了8位元組,其後給n3和n4各放了4個位元組。接著,控制的對象轉移到printf();此函數從棧中讀數,不過在這一過程中,它是在轉換說明符的指導下,讀取數值的。說明符%ld指定printf()應讀4個位元組(va_arg( va_list arg_ptr,
type )中type=long),因此printf()讀入棧中的4個位元組,作為它的第一個值。但是這隻是n1的前半部分,這個值被看成一個long整數。下一個說明符%ld讀入4個位元組,這正是n1的後半部分,這個值被看成第二個long整數。類似地,第三、第四次又讀入n2的前後兩部分。因此,儘管我們對n3和n4使用了正確的說明符,printf()仍然會產生錯誤。

轉載自http://www.codesky.net/article/200410/43133.html

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.