C语言可变参数函数取参方法

本文介绍了C语言中可变参数函数的实现原理及其使用方法,详细解释了va_start、va_arg和va_end宏的作用,并给出一个计算平均值的具体示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 熟悉C的人都知道,C语言支持可变参数函数(Variable Argument Functions),即参数的个数可以是不定个,在函数定义的时候用(...)表示,比如我们常用的printf()\execl函数等;printf函数的原型如下:

 int printf(const char *format, ...);

 注意,采用这种形式定义的可变参数函数,至少需要一个普通的形参,比如上面代码中的*format,后面的省略号是函数原型的一部分。

C语言之所以可以支持可变参数函数,一个重要的原因是C调用规范中规定C语言函数调用时,参数是从右向左压入栈的;这样一个函数实现的时候,就无需关心调用他的函数会传递几个参数过来,而只要关心自己用到几个;以printf为例:

 printf("%d%s\n",i,s);

 printf函数在定义的时候,不知道函数调用的时候会传递几个参数。在实现上,printf函数只需关心第一个参数,即字符串“%d%s\n”,当读到%d的时候,printf知道自己需要第二个参数,这时只需要去栈上寻找即可;当读到%s时,再去栈上网上寻找一个参数即可。简单说,printf不关心栈上到底压了多少参数,只关心自己需要多少。

 

 那么对于一个定义为可变参数的函数,函数定义的时候并没有定义形参原型,怎么使用参数呢?

C语言定义了一系列宏来完成可变参数函数参数的读取和使用:宏va_startva_argva_end;在ANSI C标准下,这些宏定义在stdarg.h中。三个宏的原型如下:

(1) void va_start(va_list ap, last);//取第一个可变参数(如上述printf中的i)的指针给aplast是函数声明中的最后一个固定参数(比如printf函数原型中的*fromat);

(2) type va_arg(va_list ap, type);//返回当前ap指向的可变参数的值,然后ap指向下一个可变参数;type表示当前可变参数的类型(支持的类型位intdouble);

(3) void va_end(va_list ap);//ap置为NULL

 

当一个函数被定义位可变参数函数时,其函数体内首先要定义一个va_list的结构体类型,这里沿用原型中的名字ap

 

va_start使ap指向第一个可选参数;

va_arg返回参数列表中的当前参数并使ap指向参数列表中的下一个参数;

va_endap指针清为NULL

函数体内可以多次遍历这些参数,但是都必须以va_start开始,并以va_end结尾。

 

下面是一个具体的示例(摘自wikipedia)

 #include <stdarg.h>

 double average(int count, ...)

{

    va_list ap;

    int j;

    double tot = 0;

    va_start(ap, count); //使va_list指向起始的参数

    for(j=0; j<count; j++)

        tot+=va_arg(ap, double); //检索参数,必须按照需要指定类型

    va_end(ap); //釋放va_list

    return tot/count;

 

除此之外,我们还需要注意一个陷阱,即va_arg宏的第2个参数不能被指定为charshort或者float类型。《CC++经典著作:C陷阱与缺陷》在可变参数函数传递时,因为charshort类型的参数会被提升为int类型,而float类型的参数会被提升为double类型。

 

例如,以下的代码是错误的

a = va_arg(ap,char);

 

因为我们无法传递一个char类型参数,如果传递了,它将会被自动转化为int类型。上面的式子应该写成:

 a = va_arg(ap,int); 

还需要注意的一个问题是,即时我们知道在某种体系结构下C语言函数的参数都压在栈上,我们也应该避免直接去栈上取想要的参数,因为这样会降低程序的灵活性和可移植性,并带来一些安全上潜在的危险。上述的三个宏,包括va_list,在不同的体系结构下会有不同的实现方法,比如va_list,有的系统上直接指向栈;而有的系统却将其实现为一个指针数组。

 

思考:

(1) 可变参数函数实现的原理是什么?// 查看一下vc6.0中stdarg.h中的代码:

typedef char * va_list;

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

#define va_start(ap,v)      ( ap = (va_list)&v + _INTSIZEOF(v) )

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

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

(2) 如果自己要实现这样一个fmt函数,那么该如何入手呢?// 可以参考《C语言接口与实现》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值