簡單printf實現

2021-08-10 20:15:35 字數 3787 閱讀 3244

2016-07-29 15:54

4380人閱讀收藏 

舉報

系統程式設計(12)

首先,要介紹一下printf實現的原理

printf函式原型如下:

[cpp]view plain

copy

print?

intprintf(

const

char

* format,...);  

返回值是int,返回輸出的字元個數。

例如:[cpp]view plain

copy

print?

intmain()    

測試結果:

hello world,100

返回值:16

測試結果是16,是因為100雖然是整型數,但是輸出時計算返回值它是3個字元。

引數format是乙個字元指標,指向printf裡的第乙個字串。

引數...是不定引數。這是printf能夠實現的核心。

接下來介紹一下不定引數是如何實現的。

int printf(const char* format,...);

函式的引數由右向左依次入棧,如下圖:

比如我們printf實際輸入的引數有4個,printf(char* format,arg1,arg2,arg3,arg4);

這些引數在記憶體中從低位址到高位址依次為format,arg1,arg2,arg3,arg4。

因為format是指標,所以所佔的位元組大小為乙個int的大小。

所以如果我們找到format的儲存位址,從format首位址開始,加上乙個int的大小,此時位址剛好就是引數arg1的首位址,然後再加上sizeof(arg1),此時位址又剛好是arg2的首位址,這樣我們就能依次找出引數所在位址。

具體實現時,我們只需要定義乙個指標變數ap指向arg1引數的起始位址,同時分析format引數所指的字串,從字串第乙個字元開始檢查,如果遇到%則通過分析%後面的字元就能判斷出變數的型別,此時輸出ap位址上所指向的變數的值,同時ap指標向右移動該變數型別大小位元組個單位,使ap指向下乙個引數的儲存位址,然後再次分析字串,直到分析到字串結尾結束。

通過上面的引數入棧方式我們可以得到如下結論:

如果想將棧中的引數讀出來,我們只需要知道,棧頂元素的位址即第乙個引數的位址即可。通過前面變參函式的分析,通過變參函式第乙個引數可以知道傳遞的引數個數。

當然,每個引數都有自己的型別,還有的就是位元組對齊了。在讀取引數的時候,這些問題都必須考慮到。

實際上處理變參時,已經有封裝好的巨集處理這些所有問題

[cpp]view plain

copy

print?

typedef

char

* va_list

;                  

//將char*別名為va_list;

#define   _intsizeof(n)   ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))  

#define   va_start(ap,v)   (ap = (va_list)&v + _intsizeof(v))

#define   va_arg(ap,t)     (*(t*)((ap += _intsizeof(t)) -  _intsizeof(t)))

#define   va_end(ap)      (ap = (va_list)0)

這些巨集在不同的作業系統,有不同的實現,想使用的話,只需要包含標頭檔案stdarg.h就可以了。

(1)va_start巨集的作用 : 

printf(const char* format,arg1,agr2,....)

實現ap指向第乙個實際引數arg1的位址,實際引數指第乙個引數format後的第乙個引數arg1。即va_start(ap,format)。

(2)va_arg巨集作用:

t指的是分析出來的實際引數的變數型別,首先ap向後移動sizeof(t)個單位,指向下乙個實際引數的位址,同時返回(ap-sizeof(t))的位址,返回的位址跟剛開始時位址一樣。實際上就是為了ap移動到下乙個引數的位址,為了下一次輸出。

(3)va_end巨集的作用

將ap指標賦值為null,即0

看一下實現**:

[cpp]view plain

copy

print?

#include

#include

#include

#include

void

printch(

const

char

ch)   

//輸出字元

void

printint(

const

intdec)     

//輸出整型數

printint(dec / 10);    

putchar((char

)(dec % 10 + 

'0'));    

}    

void

printstr(

const

char

*ptr)        

//輸出字串

}    

void

printfloat(

const

float

flt)     

//輸出浮點數,小數點第5位四捨五入

else

printint(tmpint);    

putchar('.'

);    

printint(tmpflt);    

}    

void

my_printf(

const

char

*format,...)    

else

case

'd':    

case

's':    

case

'f':    

default

:    

}      

}    

}  va_end(ap);           

}    

intmain()    

執行結果:

ch = a,str = hello world,dec = 1234,flt = 1234.4568

實際上,實現時可以用乙個更簡單的函式,vprintf函式。

int vprintf(char *format, va_list param);

printf的功能就是用它來實現的,所不同的是,它用乙個引數取代了變長參數列,且此引數是通過呼叫va_start巨集進行初始化。其實vprintf也是經過封裝的乙個函式。

這樣就省了我們呼叫巨集對變參函式進行處理,只要開始呼叫一次va_start巨集進行一次初始化即可。

**如下:

[cpp]view plain

copy

print?

#include

#include

intmy_printf(

char

*str,...)  

intmain()    

執行結果:

hello world,10

C語言 實現簡單的printf功能

include include include define abs x x 0 x 1 x intprintf char fmt,char tem 1024 char p1,p2,p3,ch 可變第一引數指向ap va start ap,fmt 複製格式化資料到buf strcpy buf,fmt...

printf函式實現

要實現printf函式需要考慮如下三點 1.如何告訴printf傳入引數的個數 引數個數不確定。2.printf如何訪問到這些引數。3.函式呼叫完成後,系統如何釋放在堆疊的引數。printf函式的定義 原型 int cdecl printf const char format,注 cdecl是c c...

自己實現printf

原理不是很難網上有很多,自己搜一下就明白了。void printlog const char fmt,看到上面 太簡單了,也許有人會說,這有什麼用?在我看來最大的用處在於寫日誌,如果我們把 稍稍改下就可以把螢幕上的輸出一起輸出到檔案乙份 在初始化處把全域性變數日誌檔案開啟就像這樣 plogfile ...