C 可变参数函数的本质
C语言支持定义可变参数的函数,方法是在函数的参数列表最后加上 " ... ",代表变长的参数列表,例如:
void Func(int num, ...) { }
需要注意 “...” 必须在最后,而且前面起码要有一个固定的参数,类型可以任意。
为什么要有一个固定的参数呢?这篇文章要说明的就是这个问题。
首先我们是如何调用变长参数列表里的变量?
需要使用 stdarg.h 里定义的三个宏:va_start(ap, x)、va_arg(ap,t)、va_end(ap),还有一个va_list类型(本质上是字节指针)
这几个宏的源代码:
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用于获取变长参数列表的起始地址。
使用方法是:
- 定义一个va_list类型变量,例如vlist.
- 使用宏 va_start(vlist, 最后一个固定参数) 获取变长列表的起始地址
va_list vlist;
vlist = va_start(vlist, num);
这个宏本质上是获取固定参数(如num)的下一个参数地址。原理是调用函数时,程序会将函数参数逐个压入栈中,使参数连续排列在内存中,因此只需要知道上一参数的内存地址和它的类型,就可以算出下一参数的地址。
因此这个宏等价于: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 }
当然,这样的代码移植性差,如果更改了平台很可能就会出错,使用时还是谨慎为好。
此外还有一些陷阱:
https://blog.csdn.net/smstong/article/details/50751121