在C/C++中,對函數參數的掃描是從后向前的。C/C++的函數參數是通過壓入堆棧的方式來給函數傳參數的(堆棧是一種先進后出的數據結構),最先壓入的參數最后出來,在計算機的內存中,數據有2塊,一塊是堆,一塊是棧(函數參數及局部變量在這里),而棧是從內存的高地址向低地址生長的,控制生長的就是堆棧指針了,最先壓入的參數是在最上面,就是說在所有參數的最后面,最后壓入的參數在最下面,結構上看起來是第一個,所以最后壓入的參數總是能夠被函數找到,因為它就在堆棧指針的上方。printf的第一個被找到的參數就是那個字符指針,就是被雙引號括起來的那一部分,函數通過判斷字符串里控制參數的個數來判斷參數個數及數據類型,通過這些就可算出數據需要的堆棧指針的偏移量了,下面給出printf("%d,%d",a,b);(其中a、b都是int型的)的匯編代碼.
.section
.data
string out = "%d,%d"
push b //最后的先壓入棧中
push a //最先的后壓入棧中
push $out//
參數控制的那個字符串常量是最后被壓入的
call printf
你會看到,參數是最后的先壓入棧中,最先的后壓入棧中,
參數控制的那個字符串常量是最后被壓入的,所以這個常量總是能被找到的。
通常情況下函數可變參數表的長度是已知的,通過num參數傳入,這種函數比較容易實現。
而printf函數的實現非常復雜因為
1)可變參數的個數不能輕易的得到
2)而可變參數的類型也不是固定的,需由格式字符串進行識別(由%f、%d、%s等確定)
在這個函數中,需通過對傳入的格式字符串(首地址為lpStr)進行識別來獲知可變參數個數及各個可變參數的類型,具體實現體現在for循環中。譬如,在識別為%d后,做的是va_arg ( vap, int ),而獲知為%l和%lf后則進行的是va_arg ( vap, long )、va_arg ( vap, double )。格式字符串識別完成后,可變參數也就處理完了。
在這個函數中,需通過對傳入的格式字符串(首地址為lpStr)進行識別來獲知可變參數個數及各個可變參數的類型,具體實現體現在for循環中。譬如,在識別為%d后,做的是va_arg ( vap, int ),而獲知為%l和%lf后則進行的是va_arg ( vap, long )、va_arg ( vap, double )。格式字符串識別完成后,可變參數也就處理完了。
printf的簡單實現:
#include
#include
void myitoa(int n, char str[], int radix)
{
int i , j , remain;
char tmp;
i = 0;
do
{
remain = n % radix;
if(remain > 9)
str[i] = remain - 10 + 'A';
else
str[i] = remain + '0';
i++;
}while(n /= radix);
str[i] = '\0';
for(i-- , j = 0 ; j <= i ; j++ , i--)
{
tmp = str[j];
str[j] = str[i];
str[i] = tmp;
}
}
void
myprintf(const char *format, ...)
{
char c, ch, str[30];
va_list ap;
va_start(ap, format);
while(
(c = *format))
{
switch(c)
{
case '%':
ch = *++format;
switch(ch)
{
case 'd':
{
int n = va_arg(ap, int);
myitoa(n, str, 10);
fputs(str, stdout);
break;
}
case 'x':
{
int n = va_arg(ap, int);
myitoa(n, str, 16);
fputs(str, stdout);
break;
}
case 'f':
{
double f = va_arg(ap, double);
int n;
n = f;
myitoa(n, str, 10);
fputs(str, stdout);
putchar('.');
n = (f - n) * 1000000;
myitoa(n, str, 10);
fputs(str, stdout);
break;
}
case 'c':
{
putchar(va_arg(ap, int));
break;
}
case 's':
{
char *p = va_arg(ap, char *);
fputs(p, stdout);
break;
}
case '%':
{
putchar('%');
break;
}
default:
{
fputs("format invalid!", stdout);
break;
}
}
break;
default:
putchar(c);
break;
}
format++;
}
va_end(ap);
}
int main(void)
{
myprintf("%d, %x, %f, %c, %s, %%,%a\n", 10, 15, 3.14, 'B', "hello");
return 0;
}
from:http://blog.csdn.net/hackbuteer1/article/details/7558979