可变参数函数设计
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的描述就是这些了,我们要注意的是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的
"
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的描述就是这些了,我们要注意的是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的
相关文章推荐
- 第三章 阴阳的工作机制(2)
- 绘制流水线(1)
- 三角形、平面法线、顶点法线
- 程序员的灯下黑:不要忘记你的目标
- 如何在C++中集成Lua脚本(LuaPlus篇)
- Win8&Win2012R2如何支持DOTA2输入法
- DXUT框架剖析(3)
- ALPHA混合示例
- 第三章 阴阳的工作机制(2)
- pp加速器简易去广告
- 程序员的灯下黑:不要忘记你的目标
- 一些游戏编程的书[转]
- Nebula3绘制2D纹理
- DXUT框架剖析(3)
- 在Ubuntu上安装RealPlayer的方法
- 去掉动网广告“国内使用量最大的动网论坛”的方法
- vc下使用windows的性能计数器简介
- 奇迹世界服务器架构(3)
- Nebula3绘制2D纹理
- 禁止视频网站使用Flash P2P上传