对于可变参数函数,自己一直以来都很关注,尤其当自己刚才C#上转到C时,一度曾研究C上的面向对象,将类似printf的函数归类为面向对象里的函数多态,当时只是一个主观感受。今日仔细看了下代码的实现,发现使用C提供的公共接口是可以实现的,但这些接口是如何实现的就不知了。。。
文件:stdarg.h
gcc中的定义为:
/*
Define the standard macros for the user,
if this invocation was from the user program. */
/* Define the standard macros for the user, if this invocation was from the user program. */
#define va_start(v,l) __builtin_va_start(v,l)
#define va_end(v) __builtin_va_end(v)
#define va_arg(v,l) __builtin_va_arg(v,l)
#if !defined(__STRICT_ANSI__) || __STDC_VERSION__ + 0 >= 199900L
#define va_copy(d,s) __builtin_va_copy(d,s)
#endif
if this invocation was from the user program. */
/* Define the standard macros for the user, if this invocation was from the user program. */
#define va_start(v,l) __builtin_va_start(v,l)
#define va_end(v) __builtin_va_end(v)
#define va_arg(v,l) __builtin_va_arg(v,l)
#if !defined(__STRICT_ANSI__) || __STDC_VERSION__ + 0 >= 199900L
#define va_copy(d,s) __builtin_va_copy(d,s)
#endif
下面看dbg_print(char *fmt,…)函数实现,部分内容摘自:《用库函数stdarg.h实现函数参数的可变》http://blog.sina.com.cn/s/blog_4ce3d23e0100efm3.html
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#define
MAXCHARS 512
#define MAXFRACT 10000
#define NumFract 4
static void itoa( char ** buf, int i, int base );
void itof( char ** buf, int i);
char print_buf[MAXCHARS];
void dbg_print( char * fmt,…)
{
#if (!defined(IC_MODULE_TEST))
va_list ap; // 定义一个指向个数可变的参数列表指针;
double dval;
int ival;
char * p, * sval;
char * bp, cval;
int fract;
unsigned short len;
bp = print_buf;
* bp = 0 ;
va_start (ap, fmt); // 使参数列表指针ap指向函数参数列表中的第一个可选参数,
/*
说明:fmt是位于第一个可选参数之前的固定参数,(或者说,最后一个固定参数;…之前的一个参数),函数参数列表中参数在内存中的顺序与函数声明时的顺序是一致的。如果有一va函数的声明是void va_test(char a, char b, char c, …),则它的固定参数依次是a,b,c,最后一个固定参数fmt为c,因此就是va_start(ap, c)。
*/
for (p = fmt; * p; p ++ ) // 逐个字符处理输入的fmt字符串参数
{
if ( * p != ‘ % ’)
{ // 当前字符不是%号时,直接赋值,是%表示需要对输入进行转化
* bp ++= * p;
continue ;
}
switch ( *++ p) {
case ‘d’: // 十进制数
ival = va_arg(ap, int ); // 返回参数列表中指针ap所指的参数,返回类型为int,并使指针ap指向参数列表中下一个参数。
if (ival < 0 ){
* bp ++= ‘ - ’;
ival = - ival;
}
itoa ( & bp, ival, 10 );
break ;
case ‘o’: // 八进制
ival = va_arg(ap, int );
if (ival < 0 ){
* bp ++= ‘ - ’;
ival = - ival;
}
* bp ++= ’ 0 ′;
itoa ( & bp, ival, 8 );
break ;
case ‘x’: // 16进制
ival = va_arg(ap, int );
if (ival < 0 ){
* bp ++= ‘ - ’;
ival = - ival;
}
* bp ++= ’ 0 ′;
* bp ++= ‘x’;
itoa ( & bp, ival, 16 );
break ;
case ‘c’: // 字符
cval = va_arg(ap, int );
* bp ++= cval;
break ;
case ‘f’: // 浮点数,这个处理比较特殊
dval = va_arg(ap, double );
if (dval < 0 ){
* bp ++= ‘ - ’;
dval = - dval;
}
if (dval >= 1.0 ) // 大于1.0时
itoa ( & bp, ( int )dval, 10 ); // 强制转化为整数后按十进制取小数点前的值
else // 否则直接输出0
* bp ++= ’ 0 ′;
* bp ++= ‘.’; // 设置小数点字符
fract = ( int )((dval - ( double )( int )dval) * ( double )(MAXFRACT)); // 将.后的值转化为整数
itof( & bp, fract); // 将这个整数转化为字符
break ;
case ‘s’: // string
for (sval = va_arg(ap, char * ) ; * sval ; sval ++ )
* bp ++= * sval;
break ;
}
}
// 补充一个: va_copy(dest, src):dest,src的类型都是va_list,va_copy()用于复制参数列表指针,将dest初始化为src。
* bp = 0 ;
len = (unsigned short )(bp – print_buf);
// #if 1 将数据写入串口
#ifdef __DMA_UART_VIRTUAL_FIFO__
for (bp = print_buf; * bp; bp ++ )
{
PutUARTByte(DBG_PRINT_PORT, * bp);
}
#else
BMT_PutBytes(DBG_PRINT_PORT,(kal_uint8 * )print_buf,len);
#endif
va_end (ap); // 清空参数列表,并置参数指针arg_ptr无效。说明:指针arg_ptr被置无效后,可以通过调用 va_start()、va_copy()恢复ap。每次调用va_start() / va_copy()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列表中随意地来回移动,但必须在va_start() … va_end()之内。
#endif
}
// 功能:把10进制数字转换为数字字符串。
void itof( char ** buf, int i)
{
char * s;
#define LEN 20
int rem, j;
static char rev[LEN + 1 ];
rev[LEN] = 0 ;
s = & rev[LEN]; // s指向rev末尾
for (j = 0 ; j < NumFract ; j ++ )
{
rem = i % 10 ; // 和下面i/=10配合,依次取个位,十位,百位,千位
* –s = rem + ’ 0 ′; // 加上字符0,将数字变为数字字符
i /= 10 ;
}
while ( * s)
{
( * buf)[ 0 ] = * s ++ ; // unicode 转 assic
++ ( * buf);
}
}
// 功能:把一数字转换为数字字符串,base表示type, 8 10 16 32等
static void itoa( char ** buf, int i, int base )
{
char * s;
#define LEN 20
int rem;
static char rev[LEN + 1 ];
rev[LEN] = 0 ;
if (i == 0 )
{
( * buf)[ 0 ] = ’ 0 ′;
++ ( * buf);
return ;
}
s = & rev[LEN];
while (i)
{
rem = i % base ;
if (rem < 10 )
* –s = rem + ’ 0 ′;
else if ( base == 16 )
* –s = “abcdef”[rem - 10 ];
i /= base ;
}
while ( * s)
{
( * buf)[ 0 ] = * s ++ ;
++ ( * buf);
}
}
#define MAXFRACT 10000
#define NumFract 4
static void itoa( char ** buf, int i, int base );
void itof( char ** buf, int i);
char print_buf[MAXCHARS];
void dbg_print( char * fmt,…)
{
#if (!defined(IC_MODULE_TEST))
va_list ap; // 定义一个指向个数可变的参数列表指针;
double dval;
int ival;
char * p, * sval;
char * bp, cval;
int fract;
unsigned short len;
bp = print_buf;
* bp = 0 ;
va_start (ap, fmt); // 使参数列表指针ap指向函数参数列表中的第一个可选参数,
/*
说明:fmt是位于第一个可选参数之前的固定参数,(或者说,最后一个固定参数;…之前的一个参数),函数参数列表中参数在内存中的顺序与函数声明时的顺序是一致的。如果有一va函数的声明是void va_test(char a, char b, char c, …),则它的固定参数依次是a,b,c,最后一个固定参数fmt为c,因此就是va_start(ap, c)。
*/
for (p = fmt; * p; p ++ ) // 逐个字符处理输入的fmt字符串参数
{
if ( * p != ‘ % ’)
{ // 当前字符不是%号时,直接赋值,是%表示需要对输入进行转化
* bp ++= * p;
continue ;
}
switch ( *++ p) {
case ‘d’: // 十进制数
ival = va_arg(ap, int ); // 返回参数列表中指针ap所指的参数,返回类型为int,并使指针ap指向参数列表中下一个参数。
if (ival < 0 ){
* bp ++= ‘ - ’;
ival = - ival;
}
itoa ( & bp, ival, 10 );
break ;
case ‘o’: // 八进制
ival = va_arg(ap, int );
if (ival < 0 ){
* bp ++= ‘ - ’;
ival = - ival;
}
* bp ++= ’ 0 ′;
itoa ( & bp, ival, 8 );
break ;
case ‘x’: // 16进制
ival = va_arg(ap, int );
if (ival < 0 ){
* bp ++= ‘ - ’;
ival = - ival;
}
* bp ++= ’ 0 ′;
* bp ++= ‘x’;
itoa ( & bp, ival, 16 );
break ;
case ‘c’: // 字符
cval = va_arg(ap, int );
* bp ++= cval;
break ;
case ‘f’: // 浮点数,这个处理比较特殊
dval = va_arg(ap, double );
if (dval < 0 ){
* bp ++= ‘ - ’;
dval = - dval;
}
if (dval >= 1.0 ) // 大于1.0时
itoa ( & bp, ( int )dval, 10 ); // 强制转化为整数后按十进制取小数点前的值
else // 否则直接输出0
* bp ++= ’ 0 ′;
* bp ++= ‘.’; // 设置小数点字符
fract = ( int )((dval - ( double )( int )dval) * ( double )(MAXFRACT)); // 将.后的值转化为整数
itof( & bp, fract); // 将这个整数转化为字符
break ;
case ‘s’: // string
for (sval = va_arg(ap, char * ) ; * sval ; sval ++ )
* bp ++= * sval;
break ;
}
}
// 补充一个: va_copy(dest, src):dest,src的类型都是va_list,va_copy()用于复制参数列表指针,将dest初始化为src。
* bp = 0 ;
len = (unsigned short )(bp – print_buf);
// #if 1 将数据写入串口
#ifdef __DMA_UART_VIRTUAL_FIFO__
for (bp = print_buf; * bp; bp ++ )
{
PutUARTByte(DBG_PRINT_PORT, * bp);
}
#else
BMT_PutBytes(DBG_PRINT_PORT,(kal_uint8 * )print_buf,len);
#endif
va_end (ap); // 清空参数列表,并置参数指针arg_ptr无效。说明:指针arg_ptr被置无效后,可以通过调用 va_start()、va_copy()恢复ap。每次调用va_start() / va_copy()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列表中随意地来回移动,但必须在va_start() … va_end()之内。
#endif
}
// 功能:把10进制数字转换为数字字符串。
void itof( char ** buf, int i)
{
char * s;
#define LEN 20
int rem, j;
static char rev[LEN + 1 ];
rev[LEN] = 0 ;
s = & rev[LEN]; // s指向rev末尾
for (j = 0 ; j < NumFract ; j ++ )
{
rem = i % 10 ; // 和下面i/=10配合,依次取个位,十位,百位,千位
* –s = rem + ’ 0 ′; // 加上字符0,将数字变为数字字符
i /= 10 ;
}
while ( * s)
{
( * buf)[ 0 ] = * s ++ ; // unicode 转 assic
++ ( * buf);
}
}
// 功能:把一数字转换为数字字符串,base表示type, 8 10 16 32等
static void itoa( char ** buf, int i, int base )
{
char * s;
#define LEN 20
int rem;
static char rev[LEN + 1 ];
rev[LEN] = 0 ;
if (i == 0 )
{
( * buf)[ 0 ] = ’ 0 ′;
++ ( * buf);
return ;
}
s = & rev[LEN];
while (i)
{
rem = i % base ;
if (rem < 10 )
* –s = rem + ’ 0 ′;
else if ( base == 16 )
* –s = “abcdef”[rem - 10 ];
i /= base ;
}
while ( * s)
{
( * buf)[ 0 ] = * s ++ ;
++ ( * buf);
}
}
其他地方有对这个宏的定义为:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
typedef
int
*
va_list[
1
];
#define va_start(ap, parmN) (void)(*(ap) = __va_start(parmN))
#define va_arg(ap, type) __va_arg(*(ap), type)
#define va_copy(dest, src) ((void)(*(dest) = *(src)))
#define va_end(ap) ((void)(*(ap) = 0))
#undef tolower
#undef isdigit
#define isdigit(c) (((c) >= ’0′) && ((c) <= ’9′))
#define tolower(c) (((c)>=’A’ && (c)<=’Z')?((c)+0×20):(c))
#define va_start(ap, parmN) (void)(*(ap) = __va_start(parmN))
#define va_arg(ap, type) __va_arg(*(ap), type)
#define va_copy(dest, src) ((void)(*(dest) = *(src)))
#define va_end(ap) ((void)(*(ap) = 0))
#undef tolower
#undef isdigit
#define isdigit(c) (((c) >= ’0′) && ((c) <= ’9′))
#define tolower(c) (((c)>=’A’ && (c)<=’Z')?((c)+0×20):(c))