您的位置:首页 > 其它

va_start &va_end 的使用和原理

2009-12-12 00:17 288 查看
<!--
@page { size: 21cm 29.7cm; margin: 2cm }
P { margin-bottom: 0.21cm }
-->

1:

当无法列出传递函数的所有实参的类型和数目时

,

可用省略号指定参数表

void
foo(...);

void foo(parm_list,...);

2:

函数参数的传递原理

函数参数是以数据结构

:


的形式存取

,

从右至左入栈

.

eg:

先介绍一下可变参数表的调用形式以及原理:

首先是参数的内存存放格式:

参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:

void
func(int x, float y, char z);

那么,调用函数的时候,实参

char
z

先进栈,然后是

float
y

,最后是

int
x

,因此在内存中变量的存放次序是

x->y->z

,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。

下面是

<stdarg.h>

里面重要的几个宏定义如下:

typedef
char* va_list;

void va_start ( va_list ap, prev_param ); /* ANSI
version */

type va_arg ( va_list ap, type );

void va_end (
va_list ap );

va_list

是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。

<Step
1>

在调用参数表之前,定义一个

va_list

类型的变量,

(

假设

va_list

类型变量被定义为

ap)



<Step
2>

然后应该对

ap

进行初始化,让它指向可变参数表里面的第一个参数,这是通过

va_start

来实现的,第一个参数是

ap

本身,第二个参数是在变参表前面紧挨着的一个变量

,

即“

...”

之前的那个参数;

<Step
3>

然后是获取参数,调用

va_arg

,它的第一个参数是

ap

,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把

ap

的位置指向变参表的下一个变量位置;

<Step
4>

获取所有的参数之后,我们有必要将这个

ap

指针关掉,以免发生危险,方法是调用

va_end

,他使输入的参数

ap

置为

NULL

应该养成获取完参数表之后关闭指针的习惯。

例如

int
max(int n, ...);

其函数内部应该如此实现:

int
max(int n, ...)

{
//

定参

n

表示后面变参数量,定界用,输入时切勿搞错

 

va_list
ap; //

定义一个

va_list

指针来访问参数表

va_start(ap,
n); //

初始化

ap

,让它指向第一个变参,

n

之后的参数

int
maximum = -0x7FFFFFFF; //

这是一个最小的整数

int
temp;

for(int i = 0; i < n; i++) {

temp = va_arg(ap,
int); //

获取一个

int

型参数,并且

ap

指向下一个参数

if(maximum
< temp) maximum = temp;

}

va_end(ap); //

善后工作,关闭

ap

return
max;

}

//

在主函数中测试

max

函数的行为

(C++

格式

)

int
main() {

cout << max(3, 10, 20, 30) << endl;

cout << max(6, 20, 40, 10, 50, 30, 40) <<
endl;

}

基本用法阐述至此,可以看到,这个方法存在两处极严重的漏洞


其一,输入参数的类型随意性,使得参数很容易以一个不正确的类型获取一个值

(

譬如输入一个

float

,却以

int

型去获取他

)

,这样做会出现莫名其妙的运行结果;

其二,变参表的大小并不能在运行时获取
,这样就存在一个
访问越界
的可能性,导致后果严重的

RUNTIME
ERROR



#include
<iostream>

void fun(int a, ...)

{

int *temp = &a;

temp++;

for (int i = 0; i < a; ++i)

{

cout <<
*temp << endl;

temp++;

}

}

int
main()

{

int a = 1;

int b = 2;

int c = 3;

int d =
4;

fun(4, a, b, c, d);

system("pause");

return
0;

}

Output::

1

2

3

4

3:

获取省略号指定的参数

在函数体中声明一个

va_list

,然后用

va_start

函数来获取参数列表中的参数,使用完毕后调用

va_end()

结束。像这段代码:

void
TestFun(char* pszDest, int DestLen, const char* pszFormat, ...)

{

va_list args;

va_start(args, pszFormat);
//

一定要“

...”

之前的那个参数

_vsnprintf(pszDest,
DestLen, pszFormat, args);

va_end(args);

}

4.va_start

使

argp

指向第一个可选参数。

va_arg

返回参数列表中的当前参数并使

argp

指向参数列表中的下一个参数。

va_end



argp

指针清为

NULL

。函数体内可以多次遍历这些参数,但是都必须以

va_start

开始,并以

va_end

结尾。

  

1).

演示如何使用参数个数可变的函数,采用

ANSI

标准形式

#include



stdio.h



#include



string.h



#include



stdarg.h



/*

函数原型声明,至少需要一个确定的参数,注意括号内的省略号

*/

int demo( char, ... );

void main( void )

{

demo("DEMO", "This", "is", "a",
"demo!", "");

}

/*ANSI

标准形式的声明方式,括号内的省略号表示可选参数

*/

int demo( char msg, ... )

{

/*

定义保存函数参数的结构

*/

va_list argp;

int argno = 0;

char
para;

  

/*argp

指向传入的第一个可选参数,

msg

是最后一个确定的参数

*/

va_start( argp, msg );

while (1)

{

para = va_arg( argp, char);

if ( strcmp( para, "") == 0 )

break;

printf("Parameter
#%d is: %s/n", argno, para);

argno++;

}

va_end( argp );

/*



argp

置为

NULL*/

return
0;

}

2)//

示例代码

1

:可变参数函数的使用

#include
"stdio.h"

#include "stdarg.h"

void
simple_va_fun(int start, ...)

{

va_list arg_ptr;

int
nArgValue =start;

int
nArgCout=0; //

可变参数的数目

va_start(arg_ptr,start);
//

以固定参数的地址为起点确定变参的内存起始地址。

do

{

++nArgCout;

printf("the %d th arg:
%d/n",nArgCout,nArgValue); //

输出各参数的值

nArgValue = va_arg(arg_ptr,int); //

得到下一个可变参数的值

} while(nArgValue != -1);

return;

}

int main(int argc,
char* argv[])

{

simple_va_fun(100,-1);

simple_va_fun(100,200,-1);

return 0;

}

3)//

示例代码

2:

扩展——自己实现简单的可变参数的函数。

下面是一个简单的

printf

函数的实现,参考了

<The
C Programming Language>

中的例子

#include
"stdio.h"

#include "stdlib.h"

void
myprintf(char* fmt, ...) //

一个简单的类似于

printf

的实现,

//

参数必须都是

int

类型

{

char* pArg=NULL; //

等价于原来的

va_list

char c;

pArg = (char*) &fmt; //

注意不要写成

p
= fmt !!

因为这里要对

//

参数取址,而不是取值

pArg
+= sizeof(fmt); //

等价于原来的

va_start

do{

c
=*fmt;

if (c !=



%



)

{

putchar(c); //

照原样输出字符

}

else

{ //

按格式字符输出数据

switch(*++fmt)

{

case



d



:

printf("%d",*((int*)pArg));

break;

case



x



:

printf("%#x",*((int*)pArg));

break;

default:

break;

}

pArg +=
sizeof(int); //

等价于原来的

va_arg

}

++fmt;

}while (*fmt !=



/0



);

pArg = NULL; //

等价于

va_end

return;

}

int main(int argc, char* argv[])

{

int i = 1234;

int
j = 5678;

myprintf("the first test:i=%d/n",i,j);

myprintf("the secend test:i=%d; %x;j=%d;/n",i,0xabcd,j);

system("pause");

return 0;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: