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

    使用方法是:

    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
    vadefs.h 部分代码

    知道了原理,我们其实可以直接获取变长参数列表里任意一个变量,而不用逐个获取,特别是在参数的类型都相同的情况下,例如:

     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 

     

  • 相关阅读:
    大臣的旅费 Apare_xzc 求树的直径 蓝桥杯
    连号区间数 Apare_xzc
    js盒模型
    js仿真进度条
    JS卷动事件
    json对象读取
    button属性及兼容性处理
    js图片跟随效果
    商城倒计时JS怎么做
    计算某天距离现在日期的差值
  • 原文地址:https://www.cnblogs.com/h5l0/p/12022347.html
Copyright © 2020-2023  润新知