可变参数的深入探索
2014-11-04 16:19
99 查看
在看模板的时候偶然看到了可变参数模板,于是乎想起了曾经只是听说但是从来没有仔细探究过的可变参数,这次花了两天时间终于弄的差不多了。
可变参数(variadic argument)指的是编写函数原型的时候对函数的参数是不确定的,这个特性是很强大的,也是c和C++很重要的一个特性。
要理解可变参数,首先要对C或者C++的调用协议有一点了解,在C和C++里面默认的调用协议是__cdecl,这个协议里面规定函数参数是从右到左依次压栈,而且由调用者负责清理堆栈;其余的协议例如__stdcall,这个协议的函数参数入栈顺序也是从左到右的,除了指明指针或者引用外,参数均按值传递,函数返回之前自己负责把参数从堆栈中弹出,其实发现两个协议都是差不多的,至少以目前的水平来说是看不懂的。
要深入探究可变参数,要必须了解几个宏:va_start, va_arg, va_end, _INTSIZEOF, _ADDRESSOF,接下来对这几个参数进行大致的分析和注释,这里也是本文的核心所在,因为很多有关可变参数的文章都没有涉及到这些方面,声明一下,下面的理解均是原创(下面会有更加详细的声明),并没有窃取任何人的成果。
这里要注意的是,va_arg只是检索下一个参数类型的参数列表,并不确定检索的参数是否是函数的最后一个参数。
下面谈谈实际上怎么使用这个可变参数。
从上面的讨论我们可以知道,所谓的可变参数其实编译器自己也控制不住,它只是提供一种支持而已,要使用可变参数的话还需要我们自己去设计,因此这里有两种经典的设计方案(自己YY成一前一后)
方法一:在函数的第一个参数指明参数大小
方法二:在函数的最后一个参数指明结束标志
在使用上述的宏记得要包含头文件"stdarg.h",我所了解的也就差不多了。
但是在探索源码的过程中发现了一个问题,可谓是百思不得其解啊,所以先放到这里,希望有大神可以来解答一下,同时表示自己也会尽力去寻找答案的,问题如下源码所示。
最后列举一下我的参考博客吧
link_one
link_two
link_three
转载请注明原文地址,谢谢~
可变参数(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: 1 2 4 5 6 7 */
最后列举一下我的参考博客吧
link_one
link_two
link_three
转载请注明原文地址,谢谢~
相关文章推荐
- C语言中可变参数宏的深入讨论
- 深入C语言可变参数(va_arg,va_list,va_start,va_end,_INTSIZEOF)
- 对C/C++可变参数表的深层探索
- C语言中可变参数宏的深入讨论
- 对C/C++可变参数表的深层探索
- 对C/C++可变参数表的深层探索1
- 函数可变参数深入分析之va_list、va_start、va_arg、va_end
- C/C++ 可变参数表的深层探索@
- 对C/C++可变参数表的深层探索2
- 深入了解JAVA可变长度的参数(Varargs)(键人岐)
- 深入C语言可变参数(va_arg,va_list,va_start,va_end)
- Java深入(高新技术)(二):开发环境、静态导入、可变参数、增强for循环、基本数据类型的自动拆箱与装箱、享元模式
- 对C/C++可变参数表的深层探索3
- C/C++语言可变参数表深层探索
- 深入了解JAVA可变长度的参数
- 深入java--可变参数列表
- 对c/c++可变参数表的深层探索
- C/C++语言可变参数表深层探索
- 对C/C++可变参数表的深层探索
- C/C++ 可变参数表的深层探索