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

在函数原型中,列出函数期望接受的参数,但是原型只能显示固定数目的参数。通俗来讲就是,当我们给定函数原型时候,我们也就确定了函数的参数的个数,传递参数的时候必须按照原型提供的参数个数来传递参数。
那么我们是否可以传递参数时候,提供可变个参数呢?
当然是可以的,比如我们常用的printf()函数,我们可以用以下方式传递多个参数给此函数。

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

在MSDN帮助文档中查看printf()函数的原型可以看到:
这里写图片描述
可以看到在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 = 0;
    va_start(var_arg, num);//初始化变量
    for (count = 0; count < num; count += 1)
    {
        sum += va_arg(arg, int);//访问变量
    }
    va_end(arg);//调用结束
    return sum / num;
}
int main()
{
    float aver = 0.0;
    aver=average(5, 1, 2, 3, 4, 5);
    printf("%f\n", aver);   
    system("pause");    
    return 0;
}

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

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 = 0;
    arg=(char *)(&(num))+4;
    for (count = 0; count < num; count += 1)
    {
        sum += (*(int*)((arg+=4)-4));
    }
    arg=((char *)0);
    return sum / num;
}
int main()
{
    float aver = 0.0;
    aver=average(5, 1, 2, 3, 4, 5);
    printf("%f\n", aver);   
    system("pause");    
    return 0;
}

进行栈帧分析来分析调用原理
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值