2012-09-11 17 views
17
int max(int n, ...) 

我正在使用cdecl調用約定,調用者在被調用者返回後清理變量。如何在gcc中實現可變參數?

我有興趣瞭解宏va_endva_startva_arg是如何工作的?

調用方是否將參數數組的地址作爲第二個參數傳遞給max?

回答

22

如果你看看C語言存儲在堆棧上的參數的方法,該方法宏工作應該很清楚: -

Higher memory address Last parameter 
         Penultimate parameter 
         .... 
         Second parameter 
Lower memory address  First parameter 
     StackPointer -> Return address 

(注意,這取決於硬件堆棧指針上可能是一行下降,可能會更高和更低)

參數總是像這樣存儲,即使沒有參數類型...

va_start宏只是一個指針設置到所述第一函數的參數,e.g.:-

void func (int a, ...) 
{ 
    // va_start 
    char *p = (char *) &a + sizeof a; 
} 

這使得p點到第二參數。該va_arg宏做到這一點: -

void func (int a, ...) 
{ 
    // va_start 
    char *p = (char *) &a + sizeof a; 

    // va_arg 
    int i1 = *((int *)p); 
    p += sizeof (int); 

    // va_arg 
    int i2 = *((int *)p); 
    p += sizeof (int); 

    // va_arg 
    long i2 = *((long *)p); 
    p += sizeof (long); 
} 

va_end宏僅設置pNULL

NOTES:

  1. 優化編譯器和在寄存器中一些RISC CPU的存儲參數,而不是使用堆棧。 ...參數的存在將關閉此功能並使編譯器使用堆棧。
+10

由於許多調用約定(包括常見的x64,PPC,ARM)將大部分參數傳遞到寄存器,因此這非常適合特定平臺。許多平臺不會將返回地址放在堆棧上,一個或兩個平臺的堆棧向上擴展而不是向下擴展,而某些調用約定以相反的順序將堆棧中的參數放在堆棧上。 –

+0

@Skizz:真棒回答! – Bruce

+0

@DietrichEpp:我知道。但希望它能得到一些基礎知識。我在答案中添加了一些註釋,以反映堆疊工作的各種方式。儘管如此,爲了涵蓋編譯器實現這一點的多種不同方式,需要花費更長的時間。簡單的方法是找到宏定義,並看到它們擴展到並希望沒有令人恐懼的編譯器魔法繼續。 – Skizz

8

由於參數在堆棧上傳遞,va_「函數」(它們大部分時間都是作爲宏實現的)只是操作私有堆棧指針。這個私有堆棧指針存儲在傳遞給va_start的參數中,然後va_arg在迭代參數時從「堆棧」中彈出參數。

比方說,你調用該函數max有三個參數,就像這樣:

max(a, b, c); 

裏面max功能,堆棧基本上是這樣的:

 
     +-----+ 
     | c | 
     | b | 
     | a | 
     | ret | 
SP -> +-----+ 

SP是真正的堆棧指針,它不是真正的a,bc,它們在堆棧上,但它們的值。 ret是返回地址,函數完成時跳轉到哪裏。

va_start(ap, n)做什麼是需要論證的(n在你的函數原型)的地址和計算下一個參數的位置,所以我們得到了一個新的專用堆棧指針:

 
     +-----+ 
     | c | 
ap -> | b | 
     | a | 
     | ret | 
SP -> +-----+ 

當你使用va_arg(ap, int)它返回私有堆棧指針指向的內容,然後通過將私有堆棧指針更改爲現在指向下一個參數來「彈出」它。堆棧現在看起來像這樣:

 
     +-----+ 
ap -> | c | 
     | b | 
     | a | 
     | ret | 
SP -> +-----+ 

這個描述當然是簡化的,但顯示了原理。

+0

當va_arg'無法坐在調用者清理約定中時,無法將其從堆棧彈出。 – Puppy

+0

@Joachim:你可以給一些插圖或描述你更詳細的答案。我無法想象你在說什麼。 – Bruce

+0

@DeadMG當然它不會,這就是爲什麼我在報價中加入彈出窗口的原因。 :) –

0
int max(int n, const char *msg,...) 
{ 
va_list args; 
char buffer[1024]; 
va_start(args, msg); 
nb_char_written = vsnprintf(buffer, 1024, msg, args); 
va_end(args); 
printf("(%d):%s\n",n,buffer); 
} 
+0

謝謝你的回答。我更感興趣的是如何爲被調用者設置堆棧(如何被推送)以及宏如何工作? – Bruce

相關問題