曾經在Quake源碼里看到下面一段代碼:
int VM_Call( vm_t *vm, int instruction, ... ) {
va_list argptr;
int arg0;
int arg1;
int arg2;
int arg3;
int arg4;
int arg5;
int arg6;
if( !vm ) {
Com_Error( ERR_FATAL, "VM_Call with NULL vm" );
}
//Com_Printf( "VM_Call( %i )\n", instruction );
va_start( argptr, instruction );
arg0 = va_arg( argptr, int );
arg1 = va_arg( argptr, int );
arg2 = va_arg( argptr, int );
arg3 = va_arg( argptr, int );
arg4 = va_arg( argptr, int );
arg5 = va_arg( argptr, int );
arg6 = va_arg( argptr, int );
va_end( argptr );
return vm->vmMain( instruction, arg0, arg1, arg2, arg3, arg4, arg5, arg6 );
}
引起我興趣的是VM_Call( vm_t *vm, int instruction, ... ) 中參數聲明的部分."..."這個關鍵字我是從來沒有看見過,覺得很是新奇.它的作用感覺是要騙過編譯器,使它不對vm_t *vm, int instruction以後所傳入的參數類型和個數進行檢查.換句話說就是不論函數調用者有多少參數,都可以一次性地壓入堆棧中.那麼函數本身如何去利用堆棧中的參數呢?我們還是先去看看下面系統定義的宏:
typedef char * va_list;
#ifdef _M_IX86
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
#elif defined(_M_MRX000)
/* Use these types and definitions if generating code for MIPS */
#define va_start(ap,v) ap = (va_list)&v + sizeof(v)
#define va_end(list)
#define va_arg(list, mode) ((mode *)(list = (char *) ((((int)list + (__builtin_alignof(mode)<=4?3:7)) & (__builtin_alignof(mode)<=4?-4:-8))+sizeof(mode))))[-1]
/* +++++++++++++++++++++++++++++++++++++++++++
Because of parameter passing conventions in C:
use mode=int for char, and short types
use mode=double for float types
use a pointer for array types
+++++++++++++++++++++++++++++++++++++++++++ */
#elif defined(_M_ALPHA)
我們知道函數內部使用的變量都是在堆棧中開闢空間,而且堆棧的生長方向是從高地址向低地址生長.如果跳過函數定義的第一個變量,我們就來到了存放參數的棧頂,也就是#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
這個宏的涵義.#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )則是把參數逐個的從堆棧中取出,但並不是出棧,這裡要注意.
可是為甚麼要採用這種看起來比較彆扭的方式來傳遞參數呢?主要原因是讓代碼能夠在多個編譯器上編譯,並且保證執行效果一樣.每種編譯器所定義的原始數據類型的字节長度也許是不同的.