您的位置:首页 > 编程语言 > C语言/C++

C/C++可变参数表的浅析

2007-06-19 11:30 399 查看
引言

  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平台下的内存分配顺序是从高地址内存到低地址内存

高位地址

004010C8:    第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就可以输出到目标而达到需要的效果 .
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息