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

 一、前言
      我们通常编写的函数都是参数固定的,多了少了都会有错,但是有时候我们是不能确定预先需要多少个参数的,而变长参数函数恰恰就能解决我们的问题。在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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值