简单剖析可变参数列表

在C语言中,通过将函设置为可变参数的形式,可使函数接收多个参数,下面从源码层面对该问题进行简单剖析。

先来看一段代码:

#include<stdio.h>
#include<stdarg.h>
int ave(int n,...)
{
    va_list arg;
    int i = 0;
    int sum = 0;
    va_start(arg, n);
    for (i = 0; i < n; i++)
    {
        sum += va_arg(arg, int);
    }
    va_end(arg);
    return sum / n;
}
int main()
{
    int ret = ave(4, 1, 3, 5, 7);
    printf("ret = %d", ret);
    getch();
}

在这段代码中,有一些关键点,例如:va_list arg;,va_start(arg, n);等,下面找到它的源码,进行简单剖析。

首先,对va_list转到定义,发现下图示:

这里写图片描述
char*#typedef重命名类型为va_list,所以这里可以认为va_listchar*类型,即arg是一个char*类型的变量。原来的代码就可以变成char* arg;

接着,对va_start(arg, n);三次转入定义,发现下图示:

这里写图片描述
此时,可以对照源码类比写好的代码,得到(arg = (char*)&(n)+4);,直接将(arg, n)代入(ap, v)char*代入va_list替换,可以很容易地得到((void)(arg = (char*)_ADDRESSOF(n) + _INTSIZEOF(n))
再对_ADDRESSOF(n)转到定义,发现它的源码为(&(v)),即对变量做&操作,而_INTSIZEOF(n))是对变量测字节的操作,在这段代码中,可以简单地认为完成该操作后它的值为4,原来的这句代码就可以变成(arg = (char*)&(n)+4)
这句代码的意义在于:初始化arg为未知参数列表的第一个元素的地址,而之后的+4代表指针指向的位置跳过4个字节,指向下一个元素。

然后,再对va_arg(arg,int)两次转入定义,发现:

这里写图片描述
有了上面对va_start(arg, n);的分析以后,就能轻易对va_arg(arg,int);进行类比分析,容易得到(*(int*)((arg += 4) - 4));,这句代码需要重点分析,在前面的分析中,了解到arg实际上是一个char*的指针,对指针+4指向它的下一个参数,而这时再对指针-4,再对它强制类型转换为(int*)后解引用,取到的依然是一开始指向的地址,但指针已经指向了下一个地址,为下次操作做准备。
这句代码实现了两个功能:留下了原来的地址,并使指针指向下一个地址。
这里写图片描述

最后,对va_end(arg);转到定义并替换后,得到(arg = (char*)0);,表明在结束程序后,将arg置为空指针,保证安全性。

至此,对可变参数列表的简单剖析就告一段落了,需要注意的是,可变参数存在一定的限制:
1.可变参数必须从头开始逐个访问。允许在访问了几个可变参数后终止,但不允许从一开始就访问参数裂变中间的参数;
2.参数列表中至少有一个命名参数,否则无法使用va_list
3.在转到定义后,发现它们全都是宏,这些宏不能直接判断实际存在的参数的数量,也不能判断参数的类型;
4.如果在va_arg中指定了错误的类型,后果不堪设想。

猜你喜欢

转载自blog.csdn.net/Hb_key/article/details/81385173
今日推荐