在C语言中,我们都知道给函数传参,有传址调用和传值调用的差别。但是,很少有书籍、文章专门论述到,C语言的函数传参,还有另外一大类应用,是变参处理。举个例子,我们常用的printf函数,是典型的变参函数,它的参数不固定,可以使用格式化字符控制输出格式。这个大家可能都很熟悉。

  变参函数用途很多,其通过设计,对外提供变参接口,允许上层业务层自由地通过格式化字符串来实现对自己输出行为的控制,这在很多debug和syslog日志输出场合很有用,我的书《0bug-C/C++商用工程之道》里面,第五章开篇在讲这个设计方法。这也是几乎所有C底层库进行格式化输出的基本手段。

  关于如何使用C语言变参函数,实现有效的字符串格式化处理,我想大家可能很早学会了,但是,近期几个朋友问我问题,我才发现,很多人还是不了解如何设计变参函数。正好,近期我优化我的工程库,特别重新设计的变参函数的处理方法。我这里share一下,供大家参考。

  还是那句话哈,一家之言,欢迎拍砖。

  由于前期我很多博文,我的书《0bug-C/C++商用工程之道》,都大量讲过变参处理办法,我这里不细讲了,大家有兴趣,可以看看我的SafePrintf这个函数,这在过去很多博文中都出现过了,呵呵,算是“代码明星”了。

  Code:

int SafePrintf(char* szBuf,int nMaxLength,char *szFormat, ...)  
{  
    int nListCount=0;  
    va_list pArgList;  
 
    if (!szBuf) goto SafePrintf_END_PROCESS;  
    va_start (pArgList,szFormat);  
    nListCount+=Linux_Win_vsnprintf(szBuf+nListCount,  
        nMaxLength-nListCount,szFormat,pArgList);  
    va_end(pArgList);  
    if(nListCount>(nMaxLength-1)) nListCount=nMaxLength-1;  
    *(szBuf+nListCount)='';  
 
SafePrintf_END_PROCESS:  
    return nListCount;  
}

  不过,这里面有个潜在的问题,我一直没有解决好,是说,虽然我提供了一个SafePrintf函数来处理变参,但如果另外一个函数,也提供变参界面,这时候,很不好把自己的变参参数传递给SafePrintf来处理。如下例:

  Code:

void Func(char* szFormat,...)  
{  
    char szBuf[256];  
    SafePrintf(szBuf,256,...);    //???  
}

  这样直接传递...是肯定错误的,根据ANSI C99的定义,此时要传递变参,必须使用void va_copy(va_list dst, va_list src); 这个宏来处理,以va_list这种隐式数据结构的显式拷贝动作,来把Func这个函数的变参,传递给SafePrintf。并且,由va_copy初始化的va_list在使用结束时必须使用va_end来“释放”。

  这显然太麻烦了,我以前一直很抵制这种又是显式,又是隐式,变来变去的接口方式。所以,我在《0bug-C/C++商用工程之道》这本书的库代码中,一直是把中间处理变参这段代码拷来拷去使用,哪个函数处理变参,在哪个函数一开始的地方,来上这么一段,把变参先处理成定参,再向下传递。

  不过,这也有问题,我的习惯,同样逻辑的代码只写一次,以后都是调用,避免无谓的笔误和代码冗余。这显然不符合我的习惯,所以,我也一直在想怎么优化这一块。

  近期我想了一下,决定采用函数型宏来处理这个问题,这虽然像inline一样,并不能真实地减少代码,但是,它使程序变得很简洁,程序员看起来清清爽爽,同时,由于函数型宏可以固化操作,不会再出现笔误问题,算是个比较好的折中方案。嗯,抵制使用宏的C++er们注意了哈,这是一个inline无法替代宏的实例了。呵呵。

  当然,在讨论字符串处理的前面,首先要给大家一些include的头文件,以及一些基本的定义,我呢,懒得一一分辨了,把《0bug-C/C++商用工程之道》的总跨平台include表列出来,大家直接用哈。当然,由于这些定义,下面的代码必然是跨平台的。

  Code:

#include <stdio.h>  
#include <stdlib.h>  
#include <stdarg.h>  
#include <time.h>  
#include <fcntl.h>  
#include <signal.h>  
 
#ifdef WIN32  
    #include <conio.h>  
    #include <windows.h>  
    #include <process.h>  
    #include <winsock.h>  
#else // not WIN32   
    #include <unistd.h>  
    #include <errno.h>  
    #include <pthread.h>  
    #include <fcntl.h>  
    #include <unistd.h>   
    #include <netinet/in.h>  
    #include <string.h>  
    #include <sys/time.h>  
    #include <arpa/inet.h>  
    #include <errno.h>  
    #include <termios.h>  
    #include <netdb.h>  
    #include <getopt.h>  
    #include <netinet/in.h>  
    #include <arpa/inet.h>  
    #include <unistd.h>  
#endif  
////////////////////////////////////////////////////////////////////  
#ifdef WIN32  
    #pragma warning (disable : 4800)   
    #pragma warning (disable : 4996)   
    #pragma warning (disable : 4200)   
    #pragma warning (disable : 4244)   
    #pragma warning (disable : 4010)   
    #define Linux_Win_vsnprintf _vsnprintf  
#else // not WIN32   
    #define Linux_Win_vsnprintf vsnprintf  
#endif  
#ifndef null   
    #define null 0  
#endif