可变参数函数的实现([email protected])本文通过简单的样例分析可变参数函数的实现以下分析中的地址标示在不同的机器可能不一样。
先看一下可变参数的函数实例:void Test1(int num, ...)
{
int sum = 0; va_list argList;
va_start(argList, num);
for (int i = 0; i < num; i++)
{
sum += va_arg(argList, int);
}
va_end(argList); printf("Sum is %d\n", sum);
}//call function:
int main(int argc, char* argv[])
{
Test1(3, 1, 2, 3);
} 在Window平台(win32/Intel)通常的编程情况下, va_list, va_start, va_arg, va_end
定义如下(见<stdarg.h>):
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )typedef char *  va_list;#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap)      ( ap = (va_list)0 )(1)说明:
_INTSIZEOF(n) : 以sizeof(int)为基本单位使用进一法计算变量n的size,所得结果再乘以
sizeof(int)
va_start(ap,v): 根据第一个参数的地址以及大小获取后一个参数的地址
va_arg(ap,t)  : 根据所给的类型参数t获取当前参数的值,并将va_list的指针向后移动该
参数大小指向下一个参数
va_end(ap)    : 将va_list指针设为0(无效),避免野指针(2)函数调用方式:
C/C++默认的__cdcl调用方式:从右向左压栈根据上面的知识我们就可以很轻松的解释Test1()多参数函数调用的秘密。
main()调用Test1(3, 1, 2, 3), 汇编如下:
89:       Test1(3, 1, 2, 3);
0040DB88   push        3
0040DB8A   push        2
0040DB8C   push        1
0040DB8E   push        3
0040DB90   call        @ILT+55(Test1) (0040103c)
0040DB95   add         esp,10h函数压栈后,可能的堆栈分布如下:
0X100C 3
0X1008 2
0X1004 1
0X1000 3在Test1(int num, ...)中
调用:
va_list argList;
va_start(argList, num);
得:argList ----> 0X1004第一次调用:int var = va_arg(argList, int);
argList ----> 0X1008
var = 1
...
如此循环,从而获取所有的参数在上述知识的前提下, 我们再来看如下例子:
void Test2(int& first, ...)
{
int sum = 0; va_list argList;
va_start(argList, first); for (int i = 0; i < first; i++)
{
sum += va_arg(argList, int);
}
va_end(argList); printf("Sum is %d\n", sum);
}
在main()中调用:
{
int cnt = 3;
Test2(cnt, 1, 2, 3);
}
得出的结果竟然是:5445018 ????
问题出在什么地方?
我们参考:va_start(ap, v)宏的定义:
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
当参数中包含对某个变量的引用时,ap获得地址将不是参数在堆栈中的地址,而是被传入
的变量的地址,上例中为cnt的地址。这个时候ap已经成为野指针,极有可能会导致极为
严重的bug产生。
可以做如下测试:
void Test2_1(int& first, ...)
{
// 获取first在堆栈中的地址
int* pN1 = NULL;
__asm lea eax,first;
__asm mov pN1,eax; int sum = 0; va_list argList;
va_start(argList, first);
// 可以注意到argList的地址为传入函数的&cnt+4 for (int i = 0; i < first; i++)
{
sum += va_arg(argList, int);
}
va_end(argList); printf("Sum is %d\n", sum);
}
在main()中调用:
// 注意cnt的地址 = 0x0012ff7c
Test2_1(cnt, 1, 2, 3);
察看变量的值,可以知道:
参数first在堆栈中的地址为:pN1 = 0x0012ff18
argList的地址为:0x0012ff80 = 0x0012ff18 + sizeof(first)遇到这种情况可以自行实现va_start(ap, v),可以参考
ms-help://MS.MSDNQTR.2003FEB.2052/enu_kbvisualc/en-us/visualc/Q119394.htm
给出的一种实现 
#define va_start(ap,v) {int var= _INTSIZEOF(v); \ 
                __asm lea eax,v __asm add eax,var __asm mov ap,eax \ 
                }参考文档:
ms-help://MS.MSDNQTR.2003FEB.2052/vclib/html/_crt_va_arg.2c_.va_end.2c_.va_start.htm
ms-help://MS.MSDNQTR.2003FEB.2052/enu_kbvisualc/en-us/visualc/Q119394.htm

解决方案 »

  1.   

    源码:// VarParmTest.cpp : Defines the entry point for the console application.
    //#include "stdafx.h"
    #include <stdarg.h>void Test_sprintf(const char* format, ...)
    {
    char buf[1024]; va_list argList;
    va_start(argList, format);
    vsprintf(buf, format, argList);
    va_end(argList); printf("Buffer is %s\n", buf);
    }void Test1(int num, ...)
    {
    int sum = 0; va_list argList;
    va_start(argList, num);
    for (int i = 0; i < num; i++)
    {
    sum += va_arg(argList, int);
    }
    va_end(argList); printf("Sum is %d\n", sum);
    }void Test2(int& first, ...)
    {
    int sum = 0; va_list argList;
    va_start(argList, first); for (int i = 0; i < first; i++)
    {
    sum += va_arg(argList, int);
    }
    va_end(argList); printf("Sum is %d\n", sum);
    }void Test2_1(int& first, ...)
    {
    // 获取first在堆栈中的地址
    int* pN1 = NULL;
    __asm lea eax,first;
    __asm mov pN1,eax; int sum = 0; va_list argList;
    va_start(argList, first);
    // 可以注意到argList的地址为传入函数的&cnt+4 for (int i = 0; i < first; i++)
    {
    sum += va_arg(argList, int);
    }
    va_end(argList); printf("Sum is %d\n", sum);
    }int main(int argc, char* argv[])
    {
    Test1(3, 1, 2, 3);

    int cnt = 3;
    Test2(cnt, 1, 2, 3);
    // 注意cnt的地址
    Test2_1(cnt, 1, 2, 3); return 0;
    }
      

  2.   

    argList的地址为:0x0012ff80  =  0x0012ff7c  +  sizeof(first)