#include <stdio.h>linux
#include <stdarg.h>windows
void myprintf(constchar *format, ...)數組
{函數
va_list ap;post
char c;spa
va_start(ap, format);操作系統
while ((c = *format++))指針
{orm
switch(c)對象
{
case 'c':
{
char ch = va_arg(ap, int);
putchar(ch);
break;
}
case 's':
{
char *p = va_arg(ap, char *);
fputs(p, stdout);
break;
}
default:
putchar(c);
break;
}
}
va_end(ap);
}
int main(void)
{
myprintf("s ccc\n","hello",'b','i','t');
return 0;
}
這裏要知道兩個事情:
⑴在intel+windows的機器上。函數棧的方向是向下的,棧頂指針的內存地址低於棧底指針,因此先進棧的數據是存放在內存的高地址處。
(2)在VC等絕大多數C編譯器中,默認狀況下,參數進棧的順序是由右向左的,所以,參數進棧之後的內存模型例如如下圖所看到的:最後一個固定參數的地址位於第一個可變參數之下,並且是連續存儲的。
|--------------------------|
| 最後一個可變參數 | ->高內存地址處
|--------------------------|
|--------------------------|
| 第N個可變參數 | ->va_arg(arg_ptr,int)後arg_ptr所指的地方,
| | 即第N個可變參數的地址。
|--------------- |
|--------------------------|
| 第一個可變參數 | ->va_start(arg_ptr,start)後arg_ptr所指的地方
| | 即第一個可變參數的地址
|--------------- |
|------------------------ --|
| |
| 最後一個固定參數 | -> start的起始地址
|-------------- -| .................
|-------------------------- |
| |
|--------------- | -> 低內存地址處
(4) va_arg():有了va_start的良好基礎,咱們取得了第一個可變參數的地址。在va_arg()裏的任務就是依據指定的參數類型取得本參數的值,並且把指針調到下一個參數的起始地址。
所以。現在再來看va_arg()的實現就應該心中有數了:
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
這個宏作了兩個事情,
①用用戶輸入的類型名對參數地址進行強制類型轉換,獲得用戶所需要的值
②計算出本參數的實際大小,將指針調到本參數的結尾。也就是下一個參數的首地址,以便興許處理。
(5)va_end宏的解釋:x86平臺定義爲ap=(char*)0;使ap再也不 指向堆棧,而是跟NULL同樣.有些直接定義爲((void*)0),這樣編譯器不會爲va_end產生代碼,好比gcc在linux的x86平臺就是這樣定義的. 在這裏你們要注意一個問題:由於參數的地址用於va_start宏,因此參數不能聲明爲寄存器變量或做爲函數或數組類型. 關於va_start, va_arg, va_end的描寫敘述就是這些了,咱們要注意的 是不一樣的操做系統和硬件平臺的定義有些不一樣,但原理倒是類似的.