C语言参数个数可变函数浅析
VA函数(variable argument function),参数个数可变函数,又称可变参数函数。C/C++编程中,系统提供给编程人员的va函数很少。*printf()/*scanf()系列函数,用于输入输出时格式化字符串;exec*()系列函数,用于在程序中执行外部文件(main(int argc,char*argv[])算不算呢,与其说main()也是一个可变参数函数,倒不如说它是exec*()经过封装后的具备特殊功能和意义的函数,至少在原理这一级上有很多相似之处)。由于参数个数的不确定,使va函数具有很大的灵活性,易用性,对没有使用过可变参数函数的编程人员很有诱惑力;那么,该如何编写自己的va函数,va函数的运用时机、编译实现又是如何。下面一一介绍。
一、 从printf()开始
从大家都很熟悉的格式化字符串函数开始介绍可变参数函数。
原型:int printf(const char * format, ...);
参数format表示如何来格式字符串的指令,…表示可选参数,调用时传递给"..."的参数可有可无,根据实际情况而定。系统提供了vprintf系列格式化字符串的函数,用于编程人员封装自己
的I/O函数。
int vprintf / vscanf(const char * format, va_list ap); // 从标准输入/输出格式化字符串
int vfprintf / vfsacanf(FILE * stream, const char * format, va_list ap); // 从文件流
int vsprintf / vsscanf(char * s, const char * format, va_list ap); // 从字符串
// 例1:格式化到一个文件流,可用于日志文件
FILE *logfile;
int WriteLog(const char * format, ...)
{
va_list arg_ptr;
va_start(arg_ptr, format);
int nWrittenBytes = vfprintf(logfile, format, arg_ptr);
va_end(arg_ptr);
return nWrittenBytes;
}
// 调用时,与使用printf()没有区别。
WriteLog("d-d-d d:d:d %s/d logged out.",nYear, nMonth, nDay, nHour, nMinute, szUserName, nUserID);
同理,也可以从文件中执行格式化输入;或者对标准输入输出,字符串执行格式化。
在上面的例1中,WriteLog()函数可以接受参数个数可变的输入,本质上,它的实现
需要vprintf()的支持。如何真正实现属于自己的可变参数函数,包括控制每一个传入的可选参数。
二、 va函数的定义和va宏
C语言支持va函数,作为C语言的扩展--C++同样支持va函数,但在C++中并不推荐使用,C++引入的多态性同样可以实现参数个数可变的函数。不过,C++的重载功能毕
竟只能是有限多个可以预见的参数个数。比较而言,C中的va函数则可以定义无穷多
个相当于C++的重载函数,这方面C++是无能为力的。va函数的优势表现在使用的方
便性和易用性上,可以使代码更简洁。C编译器为了统一在不同的硬件架构、硬件平
台上的实现,和增加代码的可移植性,提供了一系列宏来屏蔽硬件环境不同带来的
差异。ANSI C标准下,va的宏定义在stdarg.h中,它们有:va_list,va_start(),
va_arg(),va_end()。
// 例2:求任意个自然数的平方和:
int SqSum(int n1, ...)
{
va_list arg_ptr;
int nSqSum = 0, n = n1;
va_start(arg_ptr, n1);
while (n > 0)
{
nSqSum += (n * n);
n = va_arg(arg_ptr, int);
}
va_end(arg_ptr);
return nSqSum;
}
// 调用时
int nSqSum = SqSum(7, 2, 7, 11, -1);
可变参数函数的原型声明格式为:
type VAFunction(type arg1, type arg2, … );
参数可以分为两部分:个数确定的固定参数和个数可变的可选参数。函数至少需要
一个固定参数,固定参数的声明和普通函数一样;可选参数由于个数不确定,声明
时用"…"表示。固定参数和可选参数公同构成一个函数的参数列表。
借助上面这个简单的例2,来看看各个va_xxx的作用。
va_list arg_ptr:定义一个指向个数可变的参数列表指针(Linux0.11中为char*类型);
va_start(arg_ptr, argN):使参数列表指针arg_ptr指向函数参数列表中的第一个可选参数,说明:argN是位于第一个可选参数之前的固定参数,(或者说,最后一个固定参数;…之前
的一个参数),函数参数列表中参数在内存中的顺序与函数声明时的顺序是一致的。如果有一va函数的声明是void va_test(char a, char b, char c, …),则它的固定参数依次是a,b,c,最后一个固定参数argN为c,因此就是va_start(arg_ptr, c)。
va_arg(arg_ptr, type):返回参数列表中指针arg_ptr所指的参数,返回类型为type,并使指针arg_ptr指向参数列表中下一个参数。
va_copy(dest, src):dest,src的类型都是va_list,va_copy()用于复制参数列表指针,将dest初始化为src。
va_end(arg_ptr):清空参数列表,并置参数指针arg_ptr无效。说明:指针arg_ptr被置无效后,可以通过调用va_start()、va_copy()恢复arg_ptr。每次调用va_start() / va_copy()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列表中随意地来回移动,但必须在va_start() <-- va_end()之内。
1,首先分析 va_start 的实现方法。
下面是各个宏的具体定义和实现,这是Linux 0.11版内核的一种是实现方法。
//定义一个指向个数可变的参数列表指针
typedef char * va_list; //定义va_list 是一个字符指针类型。
/* 下面给出了类型为TYPE 的arg 参数列表所要求的空间容量。
TYPE 也可以是使用该类型的一个表达式 */
// 下面这句定义了取整后的TYPE 类型的字节长度值。是int 长度(4)的倍数。
#define __va_rounded_size(TYPE) \
(((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))
// 下面这个函数(用宏实现)使AP 指向传给函数的可变参数表的第一个参数。
// 在第一次调用va_arg 或va_end 之前,必须首先调用该函数。
// __builtin_saveregs()是在gcc 的库程序libgcc2.c 中定义的,用于保存寄存器。
// 它的说明可参见gcc 手册章节“Target Description Macros”中的
// “Implementing the Varargs Macros”小节。
//这里AP是输出,LASTARG是输入,AP是一个字符指针,LASTARG也是一个字符指针
#ifndef __sparc__ //没有分析出来这个条件编译的作用是什么??
#define va_start(AP, LASTARG) \
(AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#else
#define va_start(AP, LASTARG) \
//解释:__builtin_saveregs:根据GCC文档中的描述,由于某些函数参数
//的传递是通过寄存器来的,为了使可变函数参数机制成功,该宏将寄
//存器中的参数复制到内存中。
//解释:这个宏就是计算了可变参数中的第一个可变参数的开始地址,
//&这个符号是对LASTARG参数取地址操作
//&LASTARG求出最后一个固定参数的开始地址,再加上对齐后的LASTARG
//本身的长度便是紧接着LASTARG的第一个可变参数的地址了,再将该参数赋值给AP。
//涉及到一个C语言的参数的一个对齐机制在里面
(__builtin_saveregs (), \
AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#endif
以上就是Linux0.11内核中对va_start的一种实现放法,这里面主要涉及到C语言函数参数的通过堆栈来传递的问题了。
2,下面分析va_arg 函数的实现内幕
// 下面该宏用于扩展表达式使其与下一个被传递参数具有相同的类型和值。
// 对于缺省值,va_arg 可以用字符、无符号字符和浮点类型。
// 在第一次使用va_arg 时,它返回表中的第一个参数,后续的每次调用都将返回表的
// 下一个参数。这是通过先访问AP,然后把它增加以指向下一项来实现的。
// va_arg 使用TYPE 来完成访问和定位下一项,每调用一次va_arg,它就修改AP 以向//示表中的下一参数。
#define va_arg(AP, TYPE) \
(AP += __va_rounded_size (TYPE), \字符串和函数是什么
*((TYPE *) (AP - __va_rounded_size (TYPE))))
3,va_end 在GNU的gnulib 中有实现,由gnulib文件太多,具体实现的位置,没有到,有兴趣的可以自己查。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论