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 ...