可变参数函数的实现

lyjlee 2006-03-03 01:15:18
可变参数函数的实现(kusamba@126.com)

本文通过简单的样例分析可变参数函数的实现

以下分析中的地址标示在不同的机器可能不一样。
先看一下可变参数的函数实例:

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
...全文
210 2 打赏 收藏 转发到动态 举报
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
lyjlee 2006-03-03
  • 打赏
  • 举报
回复
argList的地址为:0x0012ff80 = 0x0012ff7c + sizeof(first)
lyjlee 2006-03-03
  • 打赏
  • 举报
回复
源码:

// 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;
}


16,470

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧