__cdecl調用
在C語言中,函數調用支持不定參數,例如printf函數,可以不知道參數的個數,eg:
void err_info(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
這是如何實現的呢?如何支持變參的呢?
下面簡要介紹下__cdecl調用方式,當然還有很多其他的調用方式,不過目前用不到
函數調用需要關注的幾個方面:
1、函數調用時參數是如何保存的?
2、是調用者來清理函數調用還是被調用者來清理函數調用?
3、函數調用時參數的順序是怎樣的?
4、可能會用到哪些寄存器?
先說第一個問題,這個是很容易的,大家都知道C語言中是用棧來保存函數調用時的參數的
下面再分析其他幾個問題:
在__cdcel標準中規定:
1、函數調用的參數入棧順序是從右到左,即先壓棧最右邊的參數,最後壓棧最左邊的參數
2、在函數調用結束後用調用者來清理棧
3、調用過程中一般會用到eax、ebx、ecx等寄存器
void func(int a, int b)
{
...
}
int main(void)
{
...
func(1,2);
...
}
下面以這個例子來說明:
在調用的過程中由於是從右到左入棧,所以壓棧的順序是先2後1,用匯編可以描述爲:
push 2
push 1
main函數對應的彙編大概是:
push 2
push 1
call func
add esp, 8 //清理棧,因爲在調用過程中esp指針向下(或者說是向低地址)移動了8個字節
ret
這是調用順序上
基於上面的幾點我們就可以理解C語言中是如何實現變參函數的調用了:
1、由於函數調用是從右到左入棧的,因此函數調用時最左邊的參數是最後一個入棧的,這樣就可以函數調用時最左邊的參數位置了
2、由於有format的指定,每個參數的大小是可知的,例如%d表示是int型,佔用4個字節等
3、根據上面的兩項就可以取出函數參數了。首先根據最左邊參數(此處的含義是最後一個確定參數,也就是該參數後面的參數都是可變參數)
(例如
void func(char *arg1, char *arg2, ...)
那麼最左邊的參數意思就是arg2,因爲它是最後一個確定參數
)
的位置可以找到函數調用時最左邊的參數,然後根據每個參數的大小,移動esp指針(一般是向高地址移動),就可以依次取出其他所有參數了
wiki上介紹:http://en.wikipedia.org/wiki/X86_calling_conventions