首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C++ >

※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG

2013-03-19 
※C++随笔※☆C++基础☆※№C++不定参数的处理实现LOGC/C语言有一个不同于其它语言的特性,即其支持可变参数,

※C++随笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG

        C/C++语言有一个不同于其它语言的特性,即其支持可变参数,典型的函数如printf、scanf等可以接受数量不定的参数。如:

其中,ebp指向保存旧的ebp的堆栈内存的下一个字的地址,ebp+8指向eip地址,ebp+12则指向函数调用的第一个参数,而ebp和esp之间是用于临时变量(也就是堆栈变量)的空间。

注意,由于上述prolog代码的存在,我们很容易通过ebp得到第一个参数的地址,对于不定参数列表之前的类型固定的参数,我们也可以根据类型信息得到其实际的位置(例如,第一个参数的位置偏移第一个参数的大小,就是第二个参数的地址)。

注意不定参数函数有个限制,就是不定参数的列表必须在整个函数的参数列表的最后。我们不可以定义如下的函数:

void func(int a, ..., int c)

所有类型固定的参数都必须出现在参数列表的开始。这样根据前面的论述,我们就可以得到所有类型固定的参数。

在设计具有不定参数列表的函数的时候,我们有两种方法来确定到底多少参数会被传递进来。

方法1是在类型固定的参数中指明后面有多少个参数以及他们的类型。printf就是采用的这种方法,它的format参数指明后面每个参数的类型。

方法2是指定一个结束参数。这种情况一般是不定参数拥有同样的类型,我们可以指定一个特定的值来表示参数列表结束。下面这个sum函数就是一个例子:

※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOGint sumi(int c, ...)
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG{
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG    va_list ap;
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG    va_start(ap,c);
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG    int i;
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG    int sum = c;
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG    c = va_arg(ap,int);
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG    while(0!=c)
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG    {
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG        sum = sum+c;
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG        c = va_arg(ap,int);
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG    }
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG    return sum;
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG}

使用这个函数的代码为:

※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOGint main(int argc, char* argv[])
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG{
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG    int i=sumi(1,2,3,4,5,6,7,8,9,0);
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG    
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG    return 0;
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG}
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG

访问各个参数

其实前文已经告诉我们怎么去访问不定参数。va_start和va_arg函数可以被结合起来用于依次访问每个函数,他们实际上都是宏函数。

在vc6,va_start函数定义为:

※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )

其中_INTSIZEOF(n)计算比n大的sizeof(int)的最小倍数,如果n=101,则_INTSIZEOF(n)为104。

va_start执行完毕后,ap指向变量v后第一个4字节对齐的地址。例如,v的地址为0x123456, v的大小为13,则v后面的下一个与字边界对齐的地址为0x123456+0x0D=0x123463再调整为与4字节对齐的下一个地址,也就是0x123464.

va_arg函数定义为:

※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

分析与va_start一样,它的结果是使ap指向当前变量的下一个变量。

这样,我们只要在开始时使用va_start把不定参数列表赋值给ap,然后依次用va_arg获得不同参数即可。

潜在问题

使用不定参数列表,有两个问题特别需要注意。

问题1的理解相对简单:我们在重载一个函数的时候,不能依赖不定参数列表部分对函数进行区分。

假定我们定义两个重载函数如下:

int func(int a, int b, ...)

int func(int a, int b, float c);

则上述函数会导致编译器不知道怎么去解释func(1,2, 3.3),因为当第三个参数为浮点数时,两个实现都可以满足匹配要求。一般情况,个人建议对于不定参数函数不要去做重载。

另外一个问题是关于类型问题。绝大多数情况下,C和C++的变量都是强类型的,而不定参数列表属于一个特例。

当我们调用va_arg的时候,我们指明下一个参数的类型,而在执行的时候,va_arg正是根据这个信息在堆栈上来找到对应的参数的。如果我们需要的类型和真实传递进来的参数完全一致时自然没有问题,但是假如类型不一样,则会有大麻烦。

假如上面的的sumi函数,我们用下面方法调用:

※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOGint sum = sumi(1, 2.2, 3, 0)

注意第二个参数我们传入了一个double类型的2.2,我们希望sumi在做加法时可以做隐式类型转换,转换为int进行计算。但是实际情况时,当我们分析到这个参数时,调用的是:

※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOGc=va_arg(ap,int)

根据前文va_arg的定义,这个宏被翻译成:

※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOG#define va_arg(ap,t)    ( *(int *)((ap += _INTSIZEOF(int)) - _INTSIZEOF(int)) )

如果后面的+=计算出正确的地址,最后就变成

*(int*)addr

如果希望能得到正确的整数值,必须要求addr所在的地址是一个真实的int类型。但是当我们传入double时,实际上其内存布局和int完全不同,因此我们得不到需要的整数。感兴趣的朋友可以用下面简单的代码做测试:

※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOGdouble a;
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOGa=1.1;
※C++漫笔※=>☆C++基础☆=>※№C++不定参数的处理实现LOGint b = *(int*) & a;

因此,当我们调用有不定参数列表的函数时,不要期望系统做隐式类型转换,系统不会做这种检查或者转换,你给的参数类型必须严格和你希望的值一样。

==============================================================================================================================

        明白以上内容了,我们就很容易用C/C++不定参数实现LOG记录了。

        新建一个Class AL_Log, 将其中构造,析构函数私有化(没必要创建这个对象,仅作为接口使用),声音一个静态方法Output



~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
以后的笔记潇汀会尽量详细讲解一些相关知识的,希望大家继续关注我的博客。
本节笔记到这里就结束了。


潇汀一有时间就会把自己的学习心得,觉得比较好的知识点写出来和大家一起分享。
编程开发的路很长很长,非常希望能和大家一起交流,共同学习,共同进步。
如果文章中有什么疏漏的地方,也请大家指正。也希望大家可以多留言来和我探讨编程相关的问题。
最后,谢谢你们一直的支持~~~



热点排行