深入剖析變長參數函數的實現【轉】
阿新 • • 發佈:2018-08-13
例如 地址 home lan pri long med fse point
轉自:https://blog.csdn.net/hailongchang/article/details/1609720
什麽是變長參數? 所謂含有變長參數的函數是指該函數可以接受可變數目的形參。例如我們都非常熟悉的 printf,scanf等等。 2:變長參數如何實現? 首先來看下面這樣一個例子: #include<stdio.h> #include<stdarg.h> #include<string.h> void demo(char *msg,...) { va_list argp; int arg_number=0; char *para = msg; va_start(argp,msg); while(1) { if ( strcmp( para, "/0") != 0 ) { arg_number++; printf("parameter %d is: %s/n",arg_number,para); } else break; para = va_arg(argp,char *); } va_end(argp); } int main() { demo("Hello","World","/0"); system("pause"); return 0; } 實現這樣一個函數要在內部使用va_list,va_start,va_arg,va_end,這些都是定義在 stdarg.h中的宏。 va_list是定義了一個保存函數參數的數據結構。 va_start(argp,msg)是將argp指向第一個可變參數,而msg是最後一個確定的參數。 最後一個確定的參數的含義是指它以後的參數都是可變參數,如果有下面的函數聲明 void demo(char *msg1,char *msg2,...) 那麽這裏的最後一個確定參數就是msg2。 va_arg(argp,char*)返回當前參數的值,類型為char *,然後將argp指向下一個變長參 數。從這一步可以看出來我們可以通過va_start和va_arg遍歷所有的變長參數。 va_end 將argp的值置為0。 下面我們看看上述幾個宏在visual c++.net 2003 中的實現方法。首先是va_list的實現 #ifdef _M_ALPHA typedef struct { char *a0; /* pointer to first homed integer argument */ int offset; /* byte offset of next parameter */ } va_list; #else typedef char * va_list; #endif 可以看到va_list實際上是一個機器類型相關的宏,除了alpha機器以外,其他機器類 型都被定義為一個char類型的指針變量,之所以定義為char *是因為可以用該變量逐 地址也就是逐字節對參數進行遍歷。 從上面可以看到,這些宏的實現都是和機器相關的,下面是大家常用的IX86機器下宏的 相關定義。 #elif defined(_M_IX86) #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define va_end(ap) ( ap = (va_list)0 ) #ifdef __cplusplus #define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) ) #else #define _ADDRESSOF(v) ( &(v) ) #endif 首先看_INTSIZEOF(n) 我們知道對於IX86,sizeof(int)一定是4的整數倍,所以~(sizeof(int) - 1) )的值一定是 右面[sizeof(n)-1]/2位為0,整個這個宏也就是保證了右面[sizeof(n)-1]/2位為0,其余位置 為1,所以_INTSIZEOF(n)的值只有可能是2,4,8,16,......等等,實際上是實現了字節對齊。 #define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) 所以va_start(ap,v)的作用就很明了了,_ADDRESSOF(v)定義了v的起始地址,_INTSIZEOF(v)定義了v所 占用的內存,所以ap 就指向v後面的參數的起始地址。 #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) ap += _INTSIZEOF(t) 使ap指向了後面一個參數的地址 而( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )相當於返回了目前t類型的參數的值。 #define va_end(ap) ( ap = (va_list)0 ) 將變量ap 的值置為0。 通過上述分析,再次印證了我麽前面對可變參數實現的解釋。 因此我們可以總結出變長參數函數的一般實現方法: 1:聲明原型,形如void demo(char *msg,...),註意變長參數的原型聲明中至少要含有 一個確定參數。 2:用va_list定義保存函數參數的數據結構,可以理解為一個指針變量(稍後會解釋)。 3:用va_start將上一步定義的變量指向第一個可變參數。 4:用va_arg遍歷所有的可變參數。 5:用va_end將指針變量持有的地址值置為0。
深入剖析變長參數函數的實現【轉】