可变参数的深入探索

来源:互联网 发布: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

转载请注明原文地址,谢谢~


0 0
原创粉丝点击