C语言可变参数的函数实现

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是指向变参的指针的类型。

2va_start用来初始化apva_list型),从该宏的内容可看出ap指向了v后面的第一个参数,通常调用此宏使ap指向第一个变参。

3va_arg是将ap指向下一个参数,t为类型,该表达式返回下一个参数的值。

4va_end是将ap0

 

 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上编译通过。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值