可变参数函数的实现([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
先看一下可变参数的函数实例: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
解决方案 »
- 最近想做一个虚拟键盘,遇到了一个问题请教
- 初学者 MFC怎么才能在点登陆后弹出另一个窗口
- 哎!!!奇怪的错误找半天没找到原因 请高手看下
- 紧急求援(ADO)只有10 分了,抱歉!
- !!高手请进!!看看这个帖子
- 如何实现在dc里输出字符时按规定rect宽度自动换行?若是英文单词的还连在一起!
- 求助:谁有截取网页上密码输入框程序的源码?急需!!!
- 一个dll中包含多个com组件,为何不会自动注册多个组件?
- 动态创建了很多按钮,如何判断在一定区域内这些按钮是否存在,并能够响应消息呢?
- 如何修改ComboBox的STYLE?
- 高手来帮忙看看,关于函数指针的问题,非高手也学习学习吧,
- 如何得到控间Edit里面写入的值?
//#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;
}