printf的聲明
int _cdecl printf(const char* format, …);
_cdecl是C和C++程序的缺省調用方式
_CDEDL調用約定:
1.參數從右到左依次入棧
2.調用者負責清理堆棧
3.參數的數量類型不會導致編譯階段的錯誤
對於x86而言,棧向下生長,函數參數從右向左入棧,因此從第一個固定參數(format)地址向前(向上)移動就可得到其他變參的地址。
va_list相關宏(VC++中stdarg.h里x86平台的宏定義)
typedef char * va_list;
//_INTSIZEOF(n)宏:將sizeof(n)按sizeof(int)對齊。
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
//取format參數之后的第一個變參地址,4字節對齊
#define va_start(va_list ap, format) ( ap = (va_list)&format+ _INTSIZEOF(format) )
//對type類型數據,先取到其四字節對齊地址,再取其值
#define va_arg(va_list ap,type)
( *(type*)((ap += _INTSIZEOF(type)) -_INTSIZEOF(type)) )
#define va_end(va_list ap) ( ap = (va_list)0 )
如何得到參數個數?
其實printf並不知道參數個數,它只是逐個解析format字符串。對於特定類型%,使用va_arg去取相應參數的值,直到遍歷字符串結束。類似於如下代碼:
#include <stdio.h>
#include <stdarg.h>
void myprintf(const char *format, ...)
{
va_list ap;
char ch;
va_start(ap, format);
while(ch = *format++)
{
switch(c)
{
case 'c':
{
char ch1 = va_arg(ap, char);
putchar(ch1);
break;
}
如下調用會打印什么內容?
printf("%x");
----------------
背景知識:
函數調用棧幀結構:
比如Z調用A,Z的棧幀:
1.自右向左(約定)壓入參數
2.Z中返回地址。即從A返回后Z中下一條指令地址
3.調用者的EBP。由編譯器插入指令實現:
"pushl %ebp"
"movl %esp, %ebp"//esp為棧指針
因而形成一個鏈表。依此可得到調用者的棧頂位置(對於A的EBP,得到Z的EBP地址為0x000f)。
4.局部變量。
對於兩個正整數 x, n 總存在整數 q, r 使得
x = nq + r, 其中 0<= r <n //最小非負剩余
q, r 是唯一確定的。q = [x/n], r = x - n[x/n]. 這個是帶余除法的一個簡單形式。在 c 語言中, q, r 容易計算出來: q = x/n, r = x % n.
所謂把 x 按 n 對齊指的是:若 r=0, 取 qn, 若 r>0, 取 (q+1)n. 這也相當於把 x 表示為:
x = nq + r', 其中 -n < r' <=0 //最大非正剩余
nq 是我們所求。關鍵是如何用 c 語言計算它。由於我們能處理標准的帶余除法,所以可以把這個式子轉換成一個標准的帶余除法,然后加以處理:
x+n = qn + (n+r'),其中 0<n+r'<=n //最大正剩余
x+n-1 = qn + (n+r'-1), 其中 0<= n+r'-1 <n //最小非負剩余
所以 qn = [(x+n-1)/n]n. 用 c 語言計算就是:
((x+n-1)/n)*n
若 n 是 2 的方冪, 比如 2^m,則除為右移 m 位,乘為左移 m 位。所以把 x+n-1 的最低 m 個二進制位清 0就可以了。得到:
(x+n-1) & (~(n-1))
