天天看點

C/C++函數參數的入棧順序,計算順序和可變參數的實作

函數參數入棧順序

#include
void foo(int x, int y, int z)
{
        printf("x = %d at [%X]\n", x, &x);
        printf("y = %d at [%X]\n", y, &y);
        printf("z = %d at [%X]\n", z, &z);
}
int main(int argc, char *argv[])
{
        foo(, , );
        return ;
}
           

運作結果是:

x = 100 at […60]

y = 200 at […64]

z = 300 at […68]

這是由于,C程式棧的記憶體生長方式是往低位址記憶體生長,這也說明為什麼局部變量無法申請太大記憶體,因為棧内容有限。此外,這個例子說明,函數參數的入棧的順序是從右往左的!。參數入棧順序具體的還與編譯器相關,涉及到C語言中調用約定所采用的方式:

C調用約定在傳回前,要作一次堆棧平衡,也就是參數入棧了多少位元組,就要彈出來多少位元組.這樣很安全.

有一點需要注意:stdcall調用約定如果采用了不定參數,即VARARG的話,則和C調用約定一樣,要由調用者來作堆棧平衡.

(1)_stdcall是 Pascal方式清理C方式壓棧,通常用于Win32 Api中,函數采用從右到左的壓棧方式,自己在退出時清空堆棧。VC将函數編譯後會在函數名前面加上下劃線字首,在函數名後加上”@”和參數的位元組數。 int f(void *p) –>> [email protected](在外部彙編語言裡可以用這個名字引用這個函數)在WIN32 API中,隻有少數幾個函數,如wspintf函數是采用C調用約定,其他都是stdcall

(2)C調用約定(即用 __cdecl關鍵字說明)(The C default calling convention)按從右至左的順序壓參數入棧,由調用者把參數彈出棧。對于傳送參數的記憶體棧是由調用者來維護的(正因為如此,實作可變參數 vararg的函數(如printf)隻能使用該調用約定)。另外,在函數名修飾約定方面也有所不同。 _cdecl是C和C++程式的預設調用方式。每一個調用它的函數都包含清空堆棧的代碼,是以産生的可執行檔案大小會比調用_stdcall函數的大。函 數采用從右到左的壓棧方式。VC将函數編譯後會在函數名前面加上下劃線字首。

(3)__fastcall調用的主 要特點就是快,因為它是通過寄存器來傳送參數的(實際上,它用ECX和EDX傳送前兩個雙字(DWORD)或更小的參數,剩下的參數仍舊自右向左壓棧傳 送,被調用的函數在傳回前清理傳送參數的記憶體棧),在函數名修飾約定方面,它和前兩者均不同。__fastcall方式的函數采用寄存器傳遞參數,VC将 函數編譯後會在函數名前面加上”@”字首,在函數名後加上”@”和參數的位元組數。

(4)thiscall僅僅應用于”C++”成員函數。this指針存放于CX/ECX寄存器中,參數從右到左壓。thiscall不是關鍵詞,是以不能被程式員指定。

(5)naked call。 當采用1-4的調用約定時,如果必要的話,進入函數時編譯器會産生代碼來儲存ESI,EDI,EBX,EBP寄存器,退出函數時則産生代碼恢複這些寄存器的内容。

綜上,其實隻有PASCAL調用約定的從左到右入棧的.而且PASCAL不能使用不定參數個數,其參數個數是一定的。

可變參數的實作

支援可變參數的__cdecl調用其實可以了解的。C方式入棧順序從右往左,那麼在棧底的元素就是可變參數的最右邊一個,我們隻需要知道所有明确參數裡的最左邊一個參數在棧中的位置,剩下到棧底的都是可變參數了,反之如果從左往右入棧,則無法知道最右邊的可變參數在棧中的位置。在具體實作中,也可觀察到其中的原理,包括,需要調用者手動清棧。

float averge(int n_values, ...)
{
    va_list var_arg;
    // 準備通路可變參數
    va_start(var_arg, n_values);// 第一個參數是va_list變量的名字,第2個參數是省略号前最後一個有名字的參數
    // 取值
    for(::)
        sum += va_arg(var_arg, int);// 第二個參數是參數的類型
    // 完成處理可變參數,手動清棧
    va_end(var_arg);
}
           

結論很簡單:如果支援可變參數的函數,那麼參數進棧的順序幾乎必然是自右向左 的。并且,參數出棧也不能由函數自己完成,而應該由調用者完成。

函數參數計算順序

主要想說明的是,函數的參數壓棧順序和參數計算順序不是一個概念。一個函數帶有多個參數的時,C++語言沒有規定函數調用時實參的求值順序。這個是編譯器自己規定的。

比方說int z = add(++x,x+y);不同編譯器可能産生不同結果。

轉載自: http://blog.csdn.net/weichaohnu/article/details/8798581

繼續閱讀