可变参数的深入探索
来源:互联网 发布:ip是设备还是网络连接 编辑:程序博客网 时间:2024/06/06 03:14
在看模板的时候偶然看到了可变参数模板,于是乎想起了曾经只是听说但是从来没有仔细探究过的可变参数,这次花了两天时间终于弄的差不多了。
可变参数(variadic argument)指的是编写函数原型的时候对函数的参数是不确定的,这个特性是很强大的,也是c和C++很重要的一个特性。
要理解可变参数,首先要对C或者C++的调用协议有一点了解,在C和C++里面默认的调用协议是__cdecl,这个协议里面规定函数参数是从右到左依次压栈,而且由调用者负责清理堆栈;其余的协议例如__stdcall,这个协议的函数参数入栈顺序也是从左到右的,除了指明指针或者引用外,参数均按值传递,函数返回之前自己负责把参数从堆栈中弹出,其实发现两个协议都是差不多的,至少以目前的水平来说是看不懂的。
要深入探究可变参数,要必须了解几个宏:va_start, va_arg, va_end, _INTSIZEOF, _ADDRESSOF,接下来对这几个参数进行大致的分析和注释,这里也是本文的核心所在,因为很多有关可变参数的文章都没有涉及到这些方面,声明一下,下面的理解均是原创(下面会有更加详细的声明),并没有窃取任何人的成果。
<span style="font-size:18px;"><span style="font-size:18px;">/*通俗一点可以理解为*#define _ADDRESSOF(v) ((char *)v)*/#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )/**宏_INTSIZEOF是为了按照整数字节的整数倍对齐指针,下面例子简写为ISO*比如ISO(char) = 4;IOS(int) = 4; ISO(double) = 8;*因为c调用协议下面,参数入栈都是整数字节(指针或者值)*/#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )/**让ap指向栈顶元素,即栈的第一个元素(栈是向上增长的,压栈会使栈顶指针变小).*这里栈的增长方向不同的人有不同的说法,不过有一点是一样的,栈底是高地址,而栈顶是底地址。*而且这个宏定义之后会实施第一次出栈(可以这么理解,虽然实际上不知道会是怎么样)*所以这个ap实际上指的就是第一个可变的参数*/#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )/**结合上面的宏可以知道,ap是指向栈顶元素的,即栈的第一个元素*每次展开这个宏后ap就会移向下一个元素,即(ap += _INTSIZEOF(t))*但是要返回之前的一个元素,即temp = (ap - _INTSIZEOF(t))*最后对算到的地址进行转换和解引用(*(t*)temp)*这里也是我之前一直没有理解的,画了无数个栈的理解图*最后终于恍然大悟,明白了到底为什么。*其实这里也是我写这篇博客的核心之核心*/#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )/*这个很容易看明白*/#define _crt_va_end(ap) ( ap = (va_list)0 )/**#define va_start _crt_va_start*#define va_end _crt_va_end#define va_arg _crt_va_arg*/</span></span>
这里要注意的是,va_arg只是检索下一个参数类型的参数列表,并不确定检索的参数是否是函数的最后一个参数。
下面谈谈实际上怎么使用这个可变参数。
从上面的讨论我们可以知道,所谓的可变参数其实编译器自己也控制不住,它只是提供一种支持而已,要使用可变参数的话还需要我们自己去设计,因此这里有两种经典的设计方案(自己YY成一前一后)
方法一:在函数的第一个参数指明参数大小
<span style="font-size:18px;">/*第一个参数设定参数的个数*//*可变参数函数*依次输出各个参数*/void va_test(int start, ...){ va_list argp; int value = start; va_start(argp, start); for (int i = 0; i < start; ++i) { value = va_arg(argp, int); cout << value << endl; } va_end(argp);}</span>
方法二:在函数的最后一个参数指明结束标志
<span style="font-size:18px;">/*最后一个参数设定参数结束标志(-1)*//*可变参数函数*依次输出各个参数*/void va_test(int start, ...){va_list argp;va_start(argp, start);while (start != -1){cout << start << endl;start = va_arg(argp, int);}va_end(argp);}</span>
在使用上述的宏记得要包含头文件"stdarg.h",我所了解的也就差不多了。
但是在探索源码的过程中发现了一个问题,可谓是百思不得其解啊,所以先放到这里,希望有大神可以来解答一下,同时表示自己也会尽力去寻找答案的,问题如下源码所示。
#include <cstdarg>#include <iostream>#include <cstring>using namespace std;void printTest(int i, ...){int sum = 0;va_list argp;va_start(argp, i);<span style="color:#FF0000;">//这个判断条件虽然说很是巧合,但是对于整数来说却是合理的(假设输入的整数里面没有0)//那么问题来了,根据va_arg(ap, t)的宏定义来说,//这里只是进行简单的地址偏移并且对地址进行类型转换和解引用,//实在是看不出来为什么在最后一个参数获取之后,//再一次调用va_arg返回的居然是一个零值,//这就是我无法理解的位置,跪求解答~</span>while (i){cout << i << endl;i = va_arg(argp, int);}va_end(argp);}int main(){printTest(1, 2, 4, 5, 6, 7);system("pause");return 0;}/*output:124567*/
最后列举一下我的参考博客吧
link_one
link_two
link_three
转载请注明原文地址,谢谢~
- 可变参数的深入探索
- 深入了解JAVA可变长度的参数
- 深入解析可变参数函数,以及可变参数常用到的宏函数解释
- 深入java--可变参数列表
- 深入了解可变参数列表
- C语言中可变参数宏的深入讨论
- C语言中可变参数宏的深入讨论
- 深入了解JAVA可变长度的参数(Varargs)
- 深入了解JAVA可变长度的参数(Varargs)(键人岐)
- 深入了解JAVA可变长度的参数(Varargs)
- 深入了解JAVA可变长度的参数(Varargs)
- 深入了解JAVA可变长度的参数(Varargs)
- 深入了解JAVA可变长度的参数(Varargs)
- 深入了解JAVA可变长度的参数(Varargs)
- 深入了解Java可变长度的参数(Varargs)
- 深入了解JAVA可变长度的参数(Varargs)
- 可变参数的应用
- 可变参数的实现
- Java Thread 总结
- linux单用户模式
- [UIApplication sharedApplication].delegate 调用全局变量
- Android有效的处理Bitmap,减少内存
- L1 L2 LASSO问题
- 可变参数的深入探索
- 中科软面试题
- Windows日志服务
- 博通发布业内首款全球导航和传感器中枢(Sensor Hub)组合芯片BCM4773
- 按照redis和mongodb
- C/C++数组名与指针区别深入探索
- android:priority
- android 关闭底部的菜单,实现真正全屏
- HTTP协议学习