c/c++语言开发共享C 可变参数函数的本质

C语言支持定义可变参数的函数,方法是在函数的参数列表最后加上 " … ",代表变长的参数列表,例如: void Func(int num, …) { } 需要注意 “…” 必须在最后,而且前面起码要有一个固定的参数,类型可以任意。 为什么要有一个固定的参数呢?这篇文章要说明的就是这个问题 …

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用于获取变长参数列表的起始地址。

使用方法是:

  1. 定义一个va_list类型变量,例如vlist.
  2. 使用宏 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 }

当然,这样的代码移植性差,如果更改了平台很可能就会出错,使用时还是谨慎为好。

此外还有一些陷阱:

 

 

 

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/c-cdevelopment/600710.html

(0)
上一篇 2021年5月9日
下一篇 2021年5月9日

精彩推荐