您的位置:首页 > 编程语言 > C语言/C++

C语言中不用宏实现变长参数函数的原理及实现

2010-11-01 23:00 399 查看
一、前言
我们通常编写的函数都是参数固定的,多了少了都会有错,但是有时候我们是不能确定预先需要多少个参数的,而变长参数函数恰恰就能解决我们的问题。在UNIX中,提供了变长参数函数的编写方法,主要是通过va_list对象实现, 定义在文件'stdarg.h'中,变长参数函数的编写有一个固定的模板,模板很简单(见下代码), 定义时, 变长参数列表通过省略号‘...’表示, 因此函数定义格式为:

type 函数名(参数1, 参数2, 参数n, . . .);

变长参数函数模板:

view plaincopy to clipboardprint?
01.#include <stdarg.h>
02.int print (char * fmt, ...)
03.{
04. va_list args;
05.
06. /* do something here */
07.
08. va_start (args, fmt);
09.
10. /* do something here */
11.
12. va_end (args);
13.
14. /* do something here*/
15.}
#include <stdarg.h>
int print (char * fmt, ...)
{
va_list args;

/* do something here */

va_start (args, fmt);

/* do something here */

va_end (args);

/* do something here*/
}

变长参数函数实例:

view plaincopy to clipboardprint?
01.#include <stdarg.h>
02.int mysum(int i, ...){ // 参数列表中, 第一个参数指示累加数的个数
03. int r = 0, j = 0;
04. va_list pvar;
05. va_start(pvar, i);
06. for(j=0;j<i;j++)
07. {
08. r += va_arg(pvar, int);
09. }
10. va_end(pvar);
11. return(r);
12.}
13.int main()
14.{
15. printf("sum(1,4) = %d/n", mysum(1,4));
16. printf("sum(2,4,8) = %d/n", mysum(2,4,8));
17. return 0;
18.}
#include <stdarg.h>
int mysum(int i, ...){ // 参数列表中, 第一个参数指示累加数的个数
int r = 0, j = 0;
va_list pvar;
va_start(pvar, i);
for(j=0;j<i;j++)
{
r += va_arg(pvar, int);
}
va_end(pvar);
return(r);
}
int main()
{
printf("sum(1,4) = %d/n", mysum(1,4));
printf("sum(2,4,8) = %d/n", mysum(2,4,8));
return 0;
}

运行结果如下:
[root]# gcc -o mysum mysum.c
[root]# ./mysum
sum(1,4) = 4
sum(2,4,8) = 12
[root]#

在上面的运行结果中已经可以看到对于同样一个函数mysum,我们两次调用时传入的参数不一样,这在通常情况下编译器会报错,但现在由于使用了变长参数,所以可以正确的执行了。

二、宏的定义
前面说过va_list, va_start, va_end都是宏,网上查了下,关于这三个宏的定义,各编译器不大一样,下面是通过网络得到的关于这三个宏的定义:
gcc中va_list的定义
#define char* va_list /* gcc中va_list等同char* */

gcc中三个宏的定义如下:
#define va_start(AP, LASTARG) ( /
AP = ((char *)& (LASTARG) + /
__va_rounded_size(LASTARG)))
通过对应第一节中的实例来分析一下这个宏,AP对应的是一个va_list对象,LASTARG自然对应的是mysum函数中的第一个参数i了,上面的意思就是首先取得第一个参数的首地址(通过(char *)& (LASTARG)这段实现的 ),然后将第一个参数首地址加上参数的大小( 通过__va_rounded_size(LASTARG)实现的),最终执行下来的结果是使AP指向mysum函数中的第二个参数。

#define va_arg(AP, TYPE) ( /
AP += __va_rounded_size(TYPE), /
*((TYPE *)(AP - __va_rounded_size(TYPE))))
其实这个宏的功能和上面的差不多,使AP指向当前参数的后面那个参数。因此,就可以通过一个循环调用这个宏,将所有参数读出了

#define va_end(AP) /* 没有定义,没有操作 */
有的编译器这样定义:
#define va_end(AP) ((void *)0) /* 有定义,是空操作 */

三.不用宏的处理变长参数实践:

从上面对宏的分析看中,了解到了宏是如何来实现变长函数的,原理如下:
1、首先获得第一个参数的地址
2、通过第一个参数地址及其大小获得下一个参数地址
3、按第2步的方式循环可以获得第3、第4、第5……直到最后一个参数地址

这是一份从网上找到的不用宏实现的变长参数函数,它实现的方式正是按上面的原理进行:

view plaincopy to clipboardprint?
01.#include <stdio.h> /* 我没包含 stdarg.h 或 vararg.h */
02.void print (char * fmt, ...)
03.{
04. char * arg; /* 变长参数的指针
05. 相当于 va_list arg */
06. int i; /* 接受int参数 */
07. double d; /* 接受double参数 */
08. char c; /* 接受char 参数*/
09. char *s; /* 接受字符串 */
10. printf ("%s", fmt); /* 打印第一个参数 fmt串 */
11. arg = (char *)&fmt + 4; /* 相当于 va_start(arg, fmt)
12. 这里的 +4 实际上是
13. sizeof(char *) 因为在IA32
14. 中,所以我写了4 没有考虑移植,
15.
16. 注意这里加 4表示arg已经指向
17. 第二个参数 */
18. /* 打印第二个参数 */
19. i = *(int *)arg; /* 接受第二个参数,
20. 为了直接了当,我硬性规定
21. print()函数的第二个
22. 参数是整数,请看
23. main()函数中的print()
24. 函数调用 */
25. printf ("%d", i); /* 打印地二个参数,是整数,
26. 所以用格式"%d" */
27. arg += sizeof(int); /* 指向下一个参数(第三个
28. 参数),为什么是加
29. sizeof(int),
30. 分析汇编码你就明白了 */
31. /* 打印第三个参数 */
32. d = *(double *)arg; /* 以下的解释同地二个参数类似,
33. 就不详细解释了 */
34. printf ("%f", d);
35. arg += sizeof(double);
36. /* 打印第四个参数 */
37. c = *(char *)arg;
38. printf ("%c", c);
39. arg += sizeof (char *);
40. /* 打印第五个参数 */
41. c = *(char *)arg;
42. printf ("%c", c);
43. arg += sizeof (char *);
44. /* 打印第六个参数 */
45. s = *(char **)arg;
46. printf ("%s", s);
47. arg += sizeof (char *);
48. arg = (void *)0; /* 使arg指针为 (void)0,
49. 实际上就上使无效,否则arg
50. 依然指向第六个参数,危险。*/
51. /* 相当于 va_end(arg) */
52.}
53.
54.int main (void)
55.{
56. print ("Hello/n", 3, 3.6, '/n', 'a', "World/n");
57. return 0;
58.}
#include <stdio.h> /* 我没包含 stdarg.h 或 vararg.h */
void print (char * fmt, ...)
{
char * arg; /* 变长参数的指针
相当于 va_list arg */
int i; /* 接受int参数 */
double d; /* 接受double参数 */
char c; /* 接受char 参数*/
char *s; /* 接受字符串 */
printf ("%s", fmt); /* 打印第一个参数 fmt串 */
arg = (char *)&fmt + 4; /* 相当于 va_start(arg, fmt)
这里的 +4 实际上是
sizeof(char *) 因为在IA32
中,所以我写了4 没有考虑移植,

注意这里加 4表示arg已经指向
第二个参数 */
/* 打印第二个参数 */
i = *(int *)arg; /* 接受第二个参数,
为了直接了当,我硬性规定
print()函数的第二个
参数是整数,请看
main()函数中的print()
函数调用 */
printf ("%d", i); /* 打印地二个参数,是整数,
所以用格式"%d" */
arg += sizeof(int); /* 指向下一个参数(第三个
参数),为什么是加
sizeof(int),
分析汇编码你就明白了 */
/* 打印第三个参数 */
d = *(double *)arg; /* 以下的解释同地二个参数类似,
就不详细解释了 */
printf ("%f", d);
arg += sizeof(double);
/* 打印第四个参数 */
c = *(char *)arg;
printf ("%c", c);
arg += sizeof (char *);
/* 打印第五个参数 */
c = *(char *)arg;
printf ("%c", c);
arg += sizeof (char *);
/* 打印第六个参数 */
s = *(char **)arg;
printf ("%s", s);
arg += sizeof (char *);
arg = (void *)0; /* 使arg指针为 (void)0,
实际上就上使无效,否则arg
依然指向第六个参数,危险。*/
/* 相当于 va_end(arg) */
}

int main (void)
{
print ("Hello/n", 3, 3.6, '/n', 'a', "World/n");
return 0;
}

代码有点长,其实很简单,只是机械地对main()函数中的print()函数中的6个参数进行处理,依次打印上面的6个参数(注意作者没有用格式化符号,带格式话符号的处理函数我将在下面给出)
运行结果如下:
/************************
Hello
33.600000
aWorld
*************************/
与预想的完全一致。说明按上面的原理对变长参数的理解是正确的。

四、后记
为什么已经有宏实现了这么一个变长参数函数的功能,我们还要去吃饱了撑的没事干,用函数来实现,何况在大多数情况下宏的运行效率要高于函数(一般来说宏产生较大的代码,但是避免了函数调用的堆栈操作,所以速度会比较快),功能也没别人强大,实现也不完善,这实在是吃力又不讨好。不过毕竟这是自己去理解、去发现、去实现的东西,很简单一个道理,就像做菜一样,虽然自己的手艺不一定比得上大厨,但终究是自己做的,吃起来自然要香于现成的。

特别说明:文章中引用了很多别人的东西,引用地址如下:http://blog.sina.com.cn/s/blog_3e7df0e5010005ip.html~type=v5_one&label=rela_nextarticle在此对作者表示由衷的感谢

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/litingli/archive/2010/01/11/5176123.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: