您的位置:首页 > 其它

可变参数的深入探索

2014-11-04 16:19 99 查看
在看模板的时候偶然看到了可变参数模板,于是乎想起了曾经只是听说但是从来没有仔细探究过的可变参数,这次花了两天时间终于弄的差不多了。

可变参数(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

转载请注明原文地址,谢谢~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: