神奇的C语言十四:可变参数

前面提及参数为空的函数默认是变参函数。那么变参函数如何使用?

(我们需要<stdarg.h>头文件)

1、我们使用va_系列函数/宏来操纵可变参数。va_系列函数包含va_start,va_arg,va_end,使用的类型包括va_list。

如何使用?

va是Variable Arguments的首字母缩写。

下面是一个计算多个值得和的函数。

#include <stdarg.h>
int Sum(int n, ...){
	int i;
	int sum = 0;
	va_list mark;
	va_start(mark, n);
	for (i = 0; i < n; ++i){
		sum += va_arg(mark, int);
	}
	return sum;
}
int main(){
	printf("%d\n", Sum( 4, 1, 2, 3, 4));
	return 0;
}

解释上面的代码:

一、mark用来记录当前解析到那个参数了。刚创建的时候指向NULL。

二、va_start的作用是让mark指向第二个参数的地址。

三、va_arg的作用是将当前mark指向的参数转变为 int 类型,返回给调用者,并将mark执行下一个参数。

其实还有一些问题:为什么va_start可以将 mark 指向第二个参数地址?va_start总是根据 n 获得了第一个参数的地址(使用取值运算符),但是并不知道 n 的字节数呀。

是这样的,C语言函数所有参数都是4个字节(对于32为程序来说)。

所有第二个参数位置就是第一个参数的内存地址+4。后面的va_arg也就是将mark加上4来获取下一个参数的位置的。

看下面这段没有使用va_系列函数/宏的等效代码:


#include <stdarg.h>
int Sum(int n, ...){
	int i;
	int sum = 0;
	char * mark = NULL;
	mark = ((char*)& n);
	mark += 4;
	for (i = 0; i < n; ++i){
		sum += * (int*) mark;
		mark += 4;
	}
	mark = 0;
	return sum;
}
int main(){
	printf("%d\n", Sum( 4, 1, 2, 3, 4));
	return 0;
}


看完后,您应该清楚va_系列函数究竟是怎么回事儿了。

您还有疑问?为什么是+4而不是-4?栈明明是向下生长的呀?

是这样的:您肯定知道C函数参数是从右往左压栈的,那您知道为什么吗?正是为了更好地实现变参函数。假设您知道函数栈的结构,那您就应该能想清楚,如果按照从右往左的顺序压栈,那么左边的参数相对于EBP的偏移就是固定的,那么就可以为变参函数生成统一的代码。

试想,如果是从左往右压栈,最左边的参数相对于EBP的偏移随着参数个数会改变。假设第一个参数是有名参数,您在变参函数中通过名称访问了第一个参数,编译器该如何为您生成代码呢?难道还要根据调用情况去生成函数的代码?可行,却不现实。

(这里还关联到函数参数计算顺序问题,这是一个老生常谈的问题了,其特性依赖于具体编译器的实现,还是不要了解并避免这个问题比较好)


您可能注意到了我没有使用 va_end这个函数,这是不好的。您可以在看看上面的等效代码,里面就有va_end,就是那句 mark = 0。你可以看到,va_end的作用是将mark重置为初始状态,并没有释放什么内存或者做什么高端的操作,常常没有丝毫作用。不过为了良好的编程风格,写上无妨。


2、常用变参函数有printf,fprintf,sprintf,vprintf,vfprintf,vsprintf。

v系列函数只是将可变参数列表...替换成了va_list类型参数。va_list参数指向...所代表的第一个参数的内存地址。如

vprintf对应于printf,他们的类型分别是:

int vsprintf( char *, va_list);
int sprintf( char *, ...);	// 这个就不举例子了

下面给出一个vsprintf的示例,在一些项目中很常见。

// 自定义的添加日志函数
void LogAppend( char * format, ... ) {
    // 可以在这里先输出时间信息到文件中
	va_list marker;					
    va_start( marker, format);	
    char buf[1024];
	vsprintf( buf, format, marker);	
	// 最后将buf写入文件中
}

您可能想到了,上面的函数类似于 fprintf。上述函数较fprintf的优势在于,您可以向输出信息中添加更多的自定义讯息,典型的有 日志记录的时间。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值