int max(int n, ...)
我正在使用cdecl
調用約定,調用者在被調用者返回後清理變量。如何在gcc中實現可變參數?
我有興趣瞭解宏va_end
,va_start
和va_arg
是如何工作的?
調用方是否將參數數組的地址作爲第二個參數傳遞給max?
int max(int n, ...)
我正在使用cdecl
調用約定,調用者在被調用者返回後清理變量。如何在gcc中實現可變參數?
我有興趣瞭解宏va_end
,va_start
和va_arg
是如何工作的?
調用方是否將參數數組的地址作爲第二個參數傳遞給max?
如果你看看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
宏僅設置p
值NULL
。
NOTES:
...
參數的存在將關閉此功能並使編譯器使用堆棧。由於參數在堆棧上傳遞,va_
「函數」(它們大部分時間都是作爲宏實現的)只是操作私有堆棧指針。這個私有堆棧指針存儲在傳遞給va_start
的參數中,然後va_arg
在迭代參數時從「堆棧」中彈出參數。
比方說,你調用該函數max
有三個參數,就像這樣:
max(a, b, c);
裏面max
功能,堆棧基本上是這樣的:
+-----+ | c | | b | | a | | ret | SP -> +-----+
SP
是真正的堆棧指針,它不是真正的a
,b
和c
,它們在堆棧上,但它們的值。 ret
是返回地址,函數完成時跳轉到哪裏。
va_start(ap, n)
做什麼是需要論證的(n
在你的函數原型)的地址和計算下一個參數的位置,所以我們得到了一個新的專用堆棧指針:
+-----+ | c | ap -> | b | | a | | ret | SP -> +-----+
當你使用va_arg(ap, int)
它返回私有堆棧指針指向的內容,然後通過將私有堆棧指針更改爲現在指向下一個參數來「彈出」它。堆棧現在看起來像這樣:
+-----+ ap -> | c | | b | | a | | ret | SP -> +-----+
這個描述當然是簡化的,但顯示了原理。
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);
}
謝謝你的回答。我更感興趣的是如何爲被調用者設置堆棧(如何被推送)以及宏如何工作? – Bruce
由於許多調用約定(包括常見的x64,PPC,ARM)將大部分參數傳遞到寄存器,因此這非常適合特定平臺。許多平臺不會將返回地址放在堆棧上,一個或兩個平臺的堆棧向上擴展而不是向下擴展,而某些調用約定以相反的順序將堆棧中的參數放在堆棧上。 –
@Skizz:真棒回答! – Bruce
@DietrichEpp:我知道。但希望它能得到一些基礎知識。我在答案中添加了一些註釋,以反映堆疊工作的各種方式。儘管如此,爲了涵蓋編譯器實現這一點的多種不同方式,需要花費更長的時間。簡單的方法是找到宏定義,並看到它們擴展到並希望沒有令人恐懼的編譯器魔法繼續。 – Skizz