在C语言中,有一种参数个数、类型不固定的函数,称之为变参函数,比如常用的printf函数。当我们在输出log信息时,也希望能写一个变参函数作为接口。这里介绍下如何写变参函数。
一、参数宏
先来看几个设计变参函数要用到的几个宏,这几个宏定义在stdarg.h文件中。
typedef char * va_list;
#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 )
1)指针类型va_list是指向变参的指针的类型。
2)va_start用来初始化ap(va_list型),从该宏的内容可看出ap指向了v后面的第一个参数,通常调用此宏使ap指向第一个变参。
3)va_arg是将ap指向下一个参数,t为类型,该表达式返回下一个参数的值。
4)va_end是将ap置0。
二、sprintf的实现
我粗略地仿sprintf写了个变参函数mysprintf,只为让大家了解一下实现细节。
CODE:
int myprintf(const char* fmt, ...){
int d, count = 0;;
char c, *s;
double f;
va_list ap;
va_start(ap, fmt);
while(0 != *fmt){
if('%' != *fmt){
putchar(*fmt);
}
else{
switch(*++fmt){
case 'd':
d = va_arg(ap, int);
putchar(d);
break;
case 'c':
c = va_arg(ap, char);
putchar(c);
break;
case 's':
s = va_arg(ap, char*);
puts(s);
break;
case 'f':
f = va_arg(ap, double);
printf("%f", f);
break;
case 'x':
d = va_arg(ap, int);
printf("%x", d);
break;
default:
break;
}
}
count++;
fmt++;
}
va_end(ap);
return count;
}
注意:上面的代码只是对sprintf部分格式的简单模仿,并不能代替sprintf函数。
三、优化变参函数
下面从两方面入手优化变参函数:
1、安全性方面
虽然sprintf输出字符串内存分配大小由调用者来控制,但免不了出现失控的局面,极易造成内存越界。
下面就是造成内存越界的情况:
char str[20];
sprintf(str, “%d*%d = %d”, a, a, a*a);
当a=10时不会引起越界,当a=10000时长度越界。
解决方法是为输出参数指明长度:
int snprintf(char *str, size_t size, const char *format, ...);
2、封装变参控制逻辑
下面对变参控制部分进行了封装。
CODE:
int hzy_snprintf(char* szout, size_t size, const char* fmt, ...){
if(NULL == szout || NULL == fmt){
return RET_FAIL;
}
int ret;
memset(szout, 0, size);
va_list ap;
va_start(ap, fmt);
#ifdef _WIN32
ret = _vsnprintf(szout, size, fmt, ap);
#else
ret = vsnprintf(szout, size, fmt, ap);
#endif
va_end(ap);
return ret;
}
上面函数中使用了库函数vsnprintf,函数原型:
int vsnprintf( char *buffer, size_t count, const char *format, va_list argptr );
注:_vsnprintf 函数是windows平台专用函数,vsnprintf函数是C标准库函数。使用了它,再也不用理会繁琐的格式控制了。
上面的封装还没有结束。本着提高代码复用性的原则,欲将上面代码应用于所有变参函数,但当遇到另外一个变参函数调用此函数时遇到了传递变参的问题。我们看到,只要把fmt传递给子函数就可以做到变参部分的封装,同时要注意va_start要取得fmt的地址,所以有两种方式封装子函数:
第一种方法:使用双指针或引用:
如:
int __hzy_snprintf(char* szout, size_t size, const char** fmt) // 双指针
int __hzy_snprintf(char* szout, size_t size, const char* &fmt) // 引用
使用双指针的CODE如下(使用引用方式的CODE略):
int __hzy_snprintf(char* szout, size_t size, const char** fmt){ // 注意,这里用了双指针
if(NULL == szout || NULL == fmt){
return RET_FAIL;
}
int ret;
memset(szout, 0, size);
va_list ap;
va_start(ap, *fmt);
#ifdef _WIN32
ret = _vsnprintf(szout, size, *fmt, ap);
#else
ret = vsnprintf(szout, size, *fmt, ap);
#endif
va_end(ap);
return ret;
}
int hzy_snprintf(char* szout, size_t size, const char* fmt, ...){
if(NULL == szout || NULL == fmt){
return RET_FAIL;
}
return __snprintf(szout, size, &fmt);
}
第二种方法:使用函数宏
CODE:
#ifdef _WIN32
#define HZY_VSNPRINTF _vsnprintf
#else
#define HZY_VSNPRINTF vsnprintf
#endif
#define HZY_SNPRINTF(szout, size, fmt) /
{ /
if(NULL == szout || NULL == fmt){ /
return RET_FAIL; /
} /
int ret; /
memset(szout, 0, size); /
va_list ap; /
va_start(ap, fmt); /
ret = HZY_VSNPRINTF(szout, size, fmt, ap); /
va_end(ap); /
return ret; /
}
int hzy_snprintf(char* szout, size_t size, const char* fmt, ...){
if(NULL == szout || NULL == fmt){
return RET_FAIL;
}
HZY_SNPRINTF(szout, size, fmt);
}
到这里关于C语言变参函数的实现说完了,上面所有代码在VC6上编译通过。