引言 C/C++ 语言有一个不同于其它语言的特性,即其支持可变参数,典型的函数如 printf :
char
name[]
=
"
RedLin
"
;
int
age
=
25
;
int
number
=
20104
; printf (
"
Hello world
"
); printf (
"
My name is %s
"
, name ); printf (
"
My age is %d ,My number is %d
"
, age, number );
第一、二、三个 printf 分别接受 1 、 2 、 3 个参数,让我们看看 printf 函数的原型:
int
printf (
const
char
*
format, ... );
从函数原型可以看出,其除了接收一个固定的参数 format 以外,后面的参数用 "…" 表示。 在 C/C++ 语言中, "…" 表示可以接受不定数量的参数,理论上来讲,可以是 0 或 0 以上的 n 个参数。
本文将对C/C++可变参数表的方法进行浅析。 1 、相关宏 标准 C/C++ 包含头文件 stdarg.h ,该头文件中定义了如下三个宏:
typedef
char
*
va_list;
#define
_INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define
va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define
va_arg(ap,type) ( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) )
#define
va_end(ap) ( ap = (va_list)0 )
对应的函数形式应为 :
void
va_start ( va_list arg_ptr, prev_param );
/**/
/* ANSI version */
type va_arg ( va_list arg_ptr, type );
void
va_end ( va_list arg_ptr );
在这些宏中, va 就是 variable argument( 可变参数 ) 的意思; arg_ptr 是指向可变参数表的指针 ; prev_param 则指可变参数表的前一个固定参数; type 为可变参数的类型。
为了能从固定参数依次得到每个可变参数, va_start,va_arg 充分利用下面两点:
1) C 语言在函数调用时,先将最后一个参数压入栈
2) X86 平台下的内存分配顺序是从高地址内存到低地址内存
高位地址
004010C 8: 第N 个可变参数
。。。
第二个可变参数
第一个可变参数 ? ap
固定参数 ? v
低位地址
结合通过 va_start 宏 我们可以取得可变参数表的首指针,其含义为将最后那个固定参数的地址加上可变参数对其的偏移后赋值给 ap ,这样 ap 就是可变参数表的首地址。
接下来,可以这样设想,如果我能确定这个可变参数的类型,那么我就知道了它占用了多少内存,依葫芦画瓢,我就能得到下一个可变参数的地址。 va_arg 宏 的意思有俩点:
1) 取出当前 ap 所指的可变参数
2) ap 指针指向下一可变参数
它先 ap 指向下一个可变参数,然后减去当前可变参数的大小即得到当前可变参数的内存地址,再做个类型转换,返回它的值。 要确定每个可变参数的类型,有两种做法,要么都是默认的类型,要么就在固定参数中包含足够的信息让程序可以确定每个可变参数的类型。比如, printf ,程序通过分析 format 字符串就可以确定每个可变参数大类型。
而 va_end 宏 被用来结束可变参数的获取 可以看出,va_end ( list ) 实际上被定义为空,没有任何真实对应的代码,用于代码对称,与 va_start 对应;另外,它还可能发挥代码的 " 自注释 " 作用。所谓代码的 " 自注释 " ,指的是代码能自己注释自己。 下面我们以实例来分析可变参数表的高级应用。
/**/
// /
//
函数名称: MyPrint
//
功能说明: 输出到终端
//
仅整数和字符串,需设置标志(%d、%l、%x、%s)
//
编者:红林
#include
<
stdarg.h
>
#include
<
iostream
>
using
namespace
std;
/**/
// /
void
MyPrint (
const
char
*
format, ... )
...
{ char tempChar; char byLen; long dwTemp; int wTemp; char * str; int i; va_list lpParam; byLen = strlen( format ); va_start ( lpParam, format ); for ( i = 0 ; i < byLen; i ++ ) ... { if ( format[i] != ' % ' ) // 不是格式符开始 ... { tempChar = format[i]; cout << tempChar; } else ... { switch (format[i + 1 ]) ... { // 整型 case ' d ' : case ' D ' : wTemp = va_arg ( lpParam, int ); i ++ ; cout << wTemp; break ; // 长整型 case ' l ' : case ' L ' : dwTemp = va_arg ( lpParam, long ); i ++ ; cout << dwTemp; break ; // 16进制(长整型) case ' x ' : case ' X ' : dwTemp = va_arg ( lpParam, long ); i ++ ; cout << hex << dwTemp; cout << dec; break ; case ' s ' : case ' S ' : str = va_arg ( lpParam, char * ); i ++ ; cout << str; break ; default : break ; } } } va_end ( lpParam ); }
在这个函数中,需通过对传入的格式字符串(首地址为 lpStr )进行识别来获知可变参数个数及各个可变参数的类型。譬如,在识别为 %d 后,做的是 va_arg ( lpParam, int ) ,而获知为 %l 和 %x 后则进行的是 va_arg ( lpParam, long ) 。格式字符串识别完成后,可变参数也就处理结束。
在开始出将printf修改成MyPrint,即可获得同样的输出.如果不是终端输出,则修改cout就可以输出到目标而达到需要的效果 .