您的位置:首页 > 其它

可变参数函数设计

2016-06-20 00:00 323 查看
#include
"
stdafx.h
"

#include
<
stdio.h
>

#include
<
stdarg.h
>

int
mul(
int
num,
int
data1,

)
{

int
total
=
data1;

int
arg,i;
va_list ap;
va_start(ap,data1);

for
(i
=
1
;i
<
num;i
++
)
{
arg
=
va_arg(ap,
int
);
total
*=
arg;
}
va_end(ap);

return
total;
}

long
mul2(
int
i,

)
{

int

*
p,j;
p
=

&
i
+
1
;
//
p指向参数列表下一个位置

long
s
=

*
p;

for
(j
=
1
;j
<
i;j
++
)
s
*=
p[j];

return
s;
}

int
main()
{
printf(
"
%d\n
"
,mul(
3
,
2
,
3
,
5
));
printf(
"
%d\n
"
,mul2(
3
,
2
,
3
,
5
));

return

0
;
}

printf的设计

#include
"
stdio.h
"

#include
"
stdlib.h
"

#include
<
stdarg.h
>

void
myprintf(
char
*
fmt, )
//
一个简单的类似于printf的实现,
//
参数必须都是int 类型

{

//
char* pArg=NULL;
//
等价于原来的va_list

va_list pArg;

char
c;

//
pArg = (char*) &fmt;
//
注意不要写成p = fmt !!因为这里要对参数取址,而不是取值

//
pArg += sizeof(fmt);
//
等价于原来的va_start

va_start(pArg,fmt);

do

{
c
=*
fmt;

if
(c
!=

'
%
'
)
{
putchar(c);
//
照原样输出字符

}

else

{
//
按格式字符输出数据

switch
(
*++
fmt)
{

case

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

break
;

case

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

break
;

case

'
f
'
:
printf(
"
%f
"
,
*
((
float
*
)pArg));

default
:

break
;
}

//
pArg += sizeof(int);
//
等价于原来的va_arg

va_arg(pArg,
int
);
}

++
fmt;
}
while
(
*
fmt
!=

'
\0
'
);

//
pArg = NULL;
//
等价于va_end

va_end(pArg);

return
;
}

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

int
i
=

1234
;

int
j
=

5678
;

myprintf(
"
the first test:i=%d
"
,i,j);
myprintf(
"
the secend test:i=%f; %x;j=%d;
"
,i,
0xabcd
,j);
system(
"
pause
"
);

return

0
;
}

可变参数在编译器中的处理

我们知道
va_start,va_arg,va_end
是在
stdarg.h
中被定义成宏的
,
由于
1)
硬件平台的不同
2)
编译器的不同
,
所以定义的宏也有所不同
,
下面以
VC++

stdarg.h

x86
平台的宏定义摘录如下
(’"’
号表示折行
):

typedef
char

*
va_list;

#define
_INTSIZEOF(n) \

((
sizeof
(n)
+
sizeof
(
int
)
-
1
)
&~
(
sizeof
(
int
)
-

1
) )

#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 )

定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.C语言的函数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址,如图:

高地址
|-----------------------------|

|
函数返回地址
|

|-----------------------------|

|
.
|

|-----------------------------|

|
第n个参数(第一个可变参数)
|

|-----------------------------|<--
va_start后ap指向

|
第n
-
1个参数(最后一个固定参数)
|

低地址
|-----------------------------|<--

&
v
图(
1
)

然后,我们用va_arg()取得类型t的可变参数值,以上例为int型为例,我们看一下va_arg取int型的返回值: j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) );
首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址(图2).然后用*取得这个地址的内容(参数值)赋给j.

高地址
|-----------------------------|

|
函数返回地址
|

|-----------------------------|

|
.
|

|-----------------------------|<--
va_arg后ap指向

|
第n个参数(第一个可变参数)
|

|-----------------------------|<--
va_start后ap指向

|
第n
-
1个参数(最后一个固定参数)
|

低地址
|-----------------------------|<--

&
v
图(
2
)

最后要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的.在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型.关于va_start, va_arg, va_end的描述就是这些了,我们要注意的是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: