天天看点

C语言可变参数列表解析及简单应用

在函数原型中,列出函数期望接受的参数,但是原型只能显示固定数目的参数。通俗来讲就是,当我们给定函数原型时候,我们也就确定了函数的参数的个数,传递参数的时候必须按照原型提供的参数个数来传递参数。

那么我们是否可以传递参数时候,提供可变个参数呢?

当然是可以的,比如我们常用的printf()函数,我们可以用以下方式传递多个参数给此函数。

printf("hello");//一个参数
printf("%d",);//两个参数
printf("%d%d",,);//三个参数
           

在MSDN帮助文档中查看printf()函数的原型可以看到:

C语言可变参数列表解析及简单应用

可以看到在printf函数参数中有由 “…”组成的参数构成,这是可变函数参数的标准格式,也是标志。我们把参数可变的函数叫做可变参数函数。

可变参数列表是通过宏来实现的,这些宏定义在头文件stdarg.h中,这个头文件中声明了一个类型va_lsit和三个宏——va_start、va_arg、va_end。

解析:

va_list类型:这个类型是通过typedef将char 类型进行了重命名,也就是说va_list是char 类型。

va_start宏:用来初始化,它的第一个参数是va_list变量的名字,第二个参数是省略号前最后一个用名字的参数。初始化过程是把va_list类型的变量设置为指向可变参数部分的第一个参数。

va_arg宏:为了访问参数,需要使用这个宏来实现,这个宏接受两个参数,va_list变量和下一个参数的类型。

va_end宏:使用这个宏来结束访问。

以下是简单实现用可变参数列表来求不同个数参数的平均值。

/*代码1*/
#include<stdio.h>
#include<stdlib.h>
#include<stdarg.h>
float average(int num, ...)
{
    va_list arg;//声明va_list类型变量arg
    int count;
    float sum = ;
    va_start(var_arg, num);//初始化变量
    for (count = ; count < num; count += )
    {
        sum += va_arg(arg, int);//访问变量
    }
    va_end(arg);//调用结束
    return sum / num;
}
int main()
{
    float aver = ;
    aver=average(, , , , , );
    printf("%f\n", aver);   
    system("pause");    
    return ;
}
           

那么可变参数列表是如何实现的呢?原理是什么呢?我们进入源码查看

typedef char *  va_list;

#define va_start _crt_va_start;
#define va_arg _crt_va_arg;
#define va_end _crt_va_end;

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

#define _ADDRESSOF(v)   ( &(v) )

#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

#define _crt_va_arg(ap,t)  ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

#define _crt_va_end(ap)   ( ap = (va_list)0 )
           

可以看到va_list类型实质是char *类型。

而va_start、va_arg、va_end经过宏定义为_crt_va_start、_crt_va_arg、_crt_va_end.

为什么要定义为_crt函数呢?首先我们得了解什么是CRT函数,一般来说,CRT函数就是标准的C语言函数。而可变参数列表是标准库的一部分。

为了对宏定义进行分析说明,我们将宏定义全部替换可得到如下:

先来解释此处的宏定义的作用:

#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

_INTSIZEOF(n)整个做的事情就是将n的长度化为int长度的整数倍。

比如n为5,二进制就是101b,int长度为4,二进制为100b,那么n化为int长度的整数倍就应该为8。

~(sizeof(int) - 1) )就应该为~(4-1)=~(00000011b)=11111100b,这样任何数& ~(sizeof(int) - 1) )后最后两位肯定为0,就肯定是4的整数倍了。

(sizeof(n) + sizeof(int) - 1)就是将大于4m但小于等于4(m+1)的数提高到大于等于4(m+1)但小于4(m+2),这样再& ~(sizeof(int) - 1) )后就正好将原长度补齐到4的倍数了。

(通俗的说,就是当n为1,2,3,4的时候n取值为4. n位5,6,7,8的时候n值取值为8.依次类推)

我们用代码1进行实例分析

float average(int num, ...)
{
    char * arg;
    int count;
    float sum = ;
    arg=(char *)(&(num))+;
    for (count = ; count < num; count += )
    {
        sum += (*(int*)((arg+=)-));
    }
    arg=((char *));
    return sum / num;
}
int main()
{
    float aver = ;
    aver=average(, , , , , );
    printf("%f\n", aver);   
    system("pause");    
    return ;
}
           

进行栈帧分析来分析调用原理

C语言可变参数列表解析及简单应用

继续阅读