深入理解可变参数(va 您所在的位置:网站首页 C语言可变参数原理 深入理解可变参数(va

深入理解可变参数(va

2024-06-08 12:21| 来源: 网络整理| 查看: 265

相关系列文章

C++模板函数重载规则细说-CSDN博客C++之std::forward(完美转发)_c++ std::forward-CSDN博客

目录

1.C语言方式

1.1.宏介绍

1.2.原理详解

1.3.宏的可变参数

1.4.案例分析

1.5.其他实例

2.C++之std::initializer_list

2.1.简介

2.2.原理详解

2.3.案例分析

3.C++之可变参数模版

3.1.简介

3.2.可变参数个数

3.3.递归包展开

3.4.逗号表达式展开

3.5.Lambda 捕获

3.6.转发参数包

4.总结

1.C语言方式 1.1.宏介绍

C语言中的可变参数是指函数可以接受可变数量的参数。这些参数的数量在编译时是未知的。在这些可变参数中的参数类型可以相同,也可以不同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活;在头文件stdarg.h中,涉及到的宏有:va_list :   是指向参数的指针 ,通过指针运算来调整访问的对象va_start :获取可变参数列表的第一个参数的地址va_arg : 获取可变参数的当前参数,返回指定类型并将指针指向下一参数va_end : 清空va_list可变参数列表

1.2.原理详解

函数的参数是存放在栈中,地址是连续的,所以可以通过相对位置去访问,这也是可变参数的访问方式;变长参数的实现需要依赖于C语言默认的cdecl调用惯例的自右向左压栈传递方式;可变参数是由1.1介绍的几个宏来实现,但是由于硬件平台的不同,编译器的不同,宏的定义也不相同,下面是AMD CPU x64平台下的定义:

typedef char* va_list;

va_list的定义

//[1] #ifdef __cplusplus #define _ADDRESSOF(v) (&reinterpret_cast(v)) #else #define _ADDRESSOF(v) (&(v)) #endif //[2] #define va_start _crt_va_start #define va_arg _crt_va_arg #define va_end _crt_va_end #define va_copy(destination, source) ((destination) = (source)) //[3] #define _PTRSIZEOF(n) ((sizeof(n) + sizeof(void*) - 1) & ~(sizeof(void*) - 1))//系统内存对齐 #define _ISSTRUCT(t) ((sizeof(t) > sizeof(void*)) || (sizeof(t) & (sizeof(t) - 1)) != 0) #define _crt_va_start(v,l) ((v) = (va_list)_ADDRESSOF(l) + _PTRSIZEOF(l)) #define _crt_va_arg(v,t) _ISSTRUCT(t) ? \ (**(t**)(((v) += sizeof(void*)) - sizeof(void*))) : \ ( *(t *)(((v) += sizeof(void*)) - sizeof(void*))) #define _crt_va_end(v) ((v) = (va_list)0) #define _crt_va_copy(d,s) ((d) = (s))

从上面的源码可以看出: 1) va_list  v; 定义一个指向char类型的指针v。 2) va_start(v,l) ;执行 v = (va_list)&l + _PTRSIZEOF(l) ,v指向参数 l 之后的那个参数的地址,即 v指向第一个可变参数在堆栈的地址。 3) va_arg(v,t) , ( (t )((v += _PTRSIZEOF(t)) - _PTRSIZEOF(t)) ) 取出当前v指针所指的值,并使 v 指向下一个参数。 v+=sizeof(t类型) ,让v指向下一个参数的地址。然后返回 v - sizeof(t类型) 的t类型指针,这正是第一个可变参数在堆栈里的地址。然后 用取得这个地址的内容。 va_end(v) ; 清空 va_list v。

备注:_PTRSIZEOF(n) 是怎么做到内存对齐的呢?

sizeof(void*)肯定是的2的整数倍,如果是32位的系统就是4,64位的系统就是8。以32位的系统为例,                                                               A)  sizeof(void*)=4 二进制是                                                0000  0100

       B)  sizeof(void*)-1=3 二进制是                                             0000  0011

       C)~(sizeof(void*) - 1)是在第二步的基础上取反,二进制是  1111   1100

 1) 当n传入int,等于4个字节,D = sizeof(int)=4,D+A = 0000 0111 , 再和C与一下,把高位和地位都清零了,算出结果 0000 0100=4,正好是32位系统对齐的字节数。

2) 当传入short,低于4个字节,D = sizeof(short)=2,D+A = 0000 110 , 再和C与一下,把高位和地位都清零了,算出结果 0000 0100=4,正好是32位系统对齐的字节数。

3)当传入double, 高于4个字节, D = sizeof(double)=8,D+A = 0001 011 ,再和C与一下,把高位和地位都清零了,算出结果 0000 1000=8,正好是double对齐的字节数。

由此可见,32位系统_PTRSIZEOF(n)小于等于4字节时,就按照4字节对齐,大于4字节时,就按照sizeof(n)对齐。

可以用此方法分析64位系统用_PTRSIZEOF(n)计算的字节对齐数,一样的原理。

1.3.宏的可变参数

标准C/C++语言宏定义的参数允许用三个小数点 ... 表示这里是可变参数,在宏替换的时候,用 __VA_ARGS__ 表示 ... 位置的所有的参数,例如:

#define example1(...) printf(__VA_ARGS__) #define example2(fmt, ...) printf(fmt, __VA_ARGS__)

很多编译器扩展了可变参数的宏替换,参数后面带三个小数点,这样的写法更容易记忆,宏定义的参数后面可以带三个小数点,表示这里是可变参数,宏替换的时候,直接写这个参数就表示这个位置是所有的可变参数了。例如:

#define example1(fmt...) printf(fmt) #define example2(fmt, args...) printf(fmt, args) 1.4.案例分析 #include #include void printValues(const char* format, ...) { va_list args; // 定义一个va_list类型的变量 va_start(args, format); // 初始化args for (const char* arg = format; *arg != '\0'; ++arg) { if (*arg == '%') { ++arg; switch (*arg) { case 'd': // 对于整数 std::cout


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有