C語言支持定義可變參數的函數,方法是在函數的參數列表最後加上 " ... ",表明變長的參數列表,例如:ide
void Func(int num, ...) { }
須要注意 「...」 必須在最後,並且前面起碼要有一個固定的參數,類型能夠任意。函數
爲何要有一個固定的參數呢?這篇文章要說明的就是這個問題。spa
首先咱們是如何調用變長參數列表裏的變量?.net
須要使用 stdarg.h 裏定義的三個宏:va_start(ap, x)、va_arg(ap,t)、va_end(ap),還有一個va_list類型(本質上是字節指針)指針
這幾個宏的源代碼:code
1 typedef char* va_list; 2
3 #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
4
5 #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
6 #define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
7 #define __crt_va_end(ap) ((void)(ap = (va_list)0))
va_start用於獲取變長參數列表的起始地址。blog
使用方法是:內存
va_list vlist; vlist = va_start(vlist, num);
這個宏本質上是獲取固定參數(如num)的下一個參數地址。原理是調用函數時,程序會將函數參數逐個壓入棧中,使參數連續排列在內存中,所以只須要知道上一參數的內存地址和它的類型,就能夠算出下一參數的地址。get
所以這個宏等價於:vlist = (char*)&num + sizeof(num);編譯器
va_arg用於按順序獲取下一個參數。
使用方法:
Type value = va_arg(vlist, Type);
本質上是對變長參數列表指針加sizeof(Type),返回累加前的地址指向的值。等價於:
Type value = *(Type*)vlist; vlist += sizeof(Type);
va_end很是簡單,就是把變長參數列表的指針置0,防止可能的錯誤。等價於:
vlist = (char*)0;
最後的簡單總結:
之因此要有一個固定參數,是由於只有知道最後一個參數的地址,才能獲取變長列表開始的地址。
此外須要注意的是,在不一樣平臺,不一樣編譯器裏,因爲內存排列有所差異(內存對齊的差異),實際狀況不必定有上面寫的等效代碼同樣簡單。具體能夠查看vadefs.h裏的定義。
1 #ifdef __cplusplus 2 #define _ADDRESSOF(v) (&const_cast<char&>(reinterpret_cast<const volatile char&>(v))) 3 #else 4 #define _ADDRESSOF(v) (&(v)) 5 #endif 6 7 #if (defined _M_ARM || defined _M_HYBRID_X86_ARM64) && !defined _M_CEE_PURE 8 #define _VA_ALIGN 4 9 #define _SLOTSIZEOF(t) ((sizeof(t) + _VA_ALIGN - 1) & ~(_VA_ALIGN - 1)) 10 #define _APALIGN(t,ap) (((va_list)0 - (ap)) & (__alignof(t) - 1)) 11 #elif defined _M_ARM64 && !defined _M_CEE_PURE 12 #define _VA_ALIGN 8 13 #define _SLOTSIZEOF(t) ((sizeof(t) + _VA_ALIGN - 1) & ~(_VA_ALIGN - 1)) 14 #define _APALIGN(t,ap) (((va_list)0 - (ap)) & (__alignof(t) - 1)) 15 #else 16 #define _SLOTSIZEOF(t) (sizeof(t)) 17 #define _APALIGN(t,ap) (__alignof(t)) 18 #endif 19 20 #if defined _M_CEE_PURE || (defined _M_CEE && !defined _M_ARM && !defined _M_ARM64) 21 22 void __cdecl __va_start(va_list*, ...); 23 void* __cdecl __va_arg(va_list*, ...); 24 void __cdecl __va_end(va_list*); 25 26 #define __crt_va_start_a(ap, v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v)))) 27 #define __crt_va_arg(ap, t) (*(t *)__va_arg(&ap, _SLOTSIZEOF(t), _APALIGN(t,ap), (t*)0)) 28 #define __crt_va_end(ap) ((void)(__va_end(&ap))) 29 30 #elif defined _M_IX86 && !defined _M_HYBRID_X86_ARM64 31 32 #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)) 33 34 #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v))) 35 #define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))) 36 #define __crt_va_end(ap) ((void)(ap = (va_list)0)) 37 38 #elif defined _M_ARM 39 40 #ifdef __cplusplus 41 void __cdecl __va_start(va_list*, ...); 42 #define __crt_va_start_a(ap, v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), _ADDRESSOF(v)))) 43 #else 44 #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _SLOTSIZEOF(v))) 45 #endif 46 47 #define __crt_va_arg(ap, t) (*(t*)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) - _SLOTSIZEOF(t))) 48 #define __crt_va_end(ap) ((void)(ap = (va_list)0)) 49 50 #elif defined _M_HYBRID_X86_ARM64 51 void __cdecl __va_start(va_list*, ...); 52 #define __crt_va_start_a(ap,v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v)))) 53 #define __crt_va_arg(ap, t) (*(t*)((ap += _SLOTSIZEOF(t)) - _SLOTSIZEOF(t))) 54 #define __crt_va_end(ap) ((void)(ap = (va_list)0)) 55 56 #elif defined _M_ARM64 57 58 void __cdecl __va_start(va_list*, ...); 59 60 #define __crt_va_start_a(ap,v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v)))) 61 #define __crt_va_arg(ap, t) \ 62 ((sizeof(t) > (2 * sizeof(__int64))) \ 63 ? **(t**)((ap += sizeof(__int64)) - sizeof(__int64)) \ 64 : *(t*)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) - _SLOTSIZEOF(t))) 65 #define __crt_va_end(ap) ((void)(ap = (va_list)0)) 66 67 68 #elif defined _M_X64 69 70 void __cdecl __va_start(va_list* , ...); 71 72 #define __crt_va_start_a(ap, x) ((void)(__va_start(&ap, x))) 73 #define __crt_va_arg(ap, t) \ 74 ((sizeof(t) > sizeof(__int64) || (sizeof(t) & (sizeof(t) - 1)) != 0) \ 75 ? **(t**)((ap += sizeof(__int64)) - sizeof(__int64)) \ 76 : *(t* )((ap += sizeof(__int64)) - sizeof(__int64))) 77 #define __crt_va_end(ap) ((void)(ap = (va_list)0)) 78 79 #endif
知道了原理,咱們其實能夠直接獲取變長參數列表裏任意一個變量,而不用逐個獲取,特別是在參數的類型都相同的狀況下,例如:
1 int Sum(int count, ...) 2 { 3 int sum = 0; 4
5 for (int i = 0; i < count; i++) 6 { 7 sum += *(int *)((char *)&count + sizeof(int) * (i + 1)); 8 } 9
10 return sum; 11 }
固然,這樣的代碼移植性差,若是更改了平臺極可能就會出錯,使用時仍是謹慎爲好。
此外還有一些陷阱: