首先列出我自己实际遇到的一个例子:
在串口向 PC 发送数据时为了实现可变参数的功能,这是工程中遇到的一段代码:
int SerialDbgPrintf(uint8 type, char *fmt, ...)
{
if(type == ATCMD)
{
int cnt;
char string[MAX_PRINTF_STR_SIZE] = {'\0'};
va_list ap;
va_start(ap,fmt);
//cnt = vsprintf(string, fmt, ap);
cnt = vsnprintf(string,MAX_PRINTF_STR_SIZE ,fmt, ap);
if(cnt > 0)
{
//PutStrToUart1Dbg(string,strlen((char *)string));
if(cnt < MAX_PRINTF_STR_SIZE)
PutStrToUart1Dbg(string,cnt);
else
PutStrToUart1Dbg(string,MAX_PRINTF_STR_SIZE);
}
va_end(ap);
return (cnt);
}
else if(type == NRCMD)
{
if(gDeviceConfig.DbgCtl.NormalInfoEn == TRUE)
{
int cnt;
char string[MAX_PRINTF_STR_SIZE] = {'\0'};
va_list ap;
va_start(ap,fmt);
//cnt = vsprintf(string, fmt, ap);
cnt = vsnprintf(string,MAX_PRINTF_STR_SIZE ,fmt, ap);
if(cnt > 0)
{
//PutStrToUart1Dbg(string,strlen((char *)string));
if(cnt < MAX_PRINTF_STR_SIZE)
PutStrToUart1Dbg(string,cnt);
else
PutStrToUart1Dbg(string,MAX_PRINTF_STR_SIZE);
}
va_end(ap);
return (cnt);
}
else
{
return 0;
}
}
return -1;
}
C语言va_list与_vsnprintf的使用
先举一个例子: #define bufsize 80 /* 这个函数用来格式化带参数的字符串*/ //将带参数的字符串按照参数列表格式化到buffer中 int main(int argc, char* argv[]) 下面我们来探讨如何写一个简单的可变参数的C函数.
写可变参数的C函数要在程序中用到以下这些宏: 使用可变参数应该有以下步骤: 可变参数在编译器中的处理 我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,由于: 1)硬件平台的不同 2)编译器的不同 Microsoft Visual Studio\VC98\Include\stdarg.h中, /*_INTSIZEOF (n)宏是为了考虑那些内存地址需要对齐的系统,从宏的名字来应该是跟sizeof(int)对齐。一般的sizeof(int)=4,也就是参数在内存中的地址都为4的倍数。比如,如果sizeof(n)在1-4之间,那么_INTSIZEOF(n)=4;如果sizeof(n)在5-8之间,那么 _INTSIZEOF(n)=8。*/ /*va_start 的定义为 &v+_INTSIZEOF(v) ,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start (ap, v)以后,ap指向第一个可变参数在的内存地址*/ /*这个宏做了两个事情, ①用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值 ②计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。*/
C语言的函数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址,如图: 高地址|-------------------------------------------| 低地址|-------------------------------------------|<-- &v 然后,我们用va_arg()取得类型t的可变参数值,以上例为int型为例,我们看一下va_arg取int型的返回值: j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) ); 高地址|--------------------------------------------| 低地址|--------------------------------------------|<-- &v 最后要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 可变参数在编程中要注意的问题 因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型. va_start(arg_ptr, i); 可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实现跟堆栈相关.我们写一个可变函数的C函数时,有利也有弊,所以在不必要的场合,我们无需用到可变参数.如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现. |