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

《转》 

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

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

     
         变长参数函数模板:
 
       
view plain copy to clipboard print ?
  1. #include <stdarg.h>     
  2. int  print ( char  * fmt, ...)  
  3. {  
  4.     va_list  args;  
  5.   
  6.     /* do something here */   
  7.   
  8.     va_start (args, fmt);  
  9.    
  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 plain copy to clipboard print ?
  1. #include <stdarg.h>   
  2. int  mysum( int  i, ...){  // 参数列表中, 第一个参数指示累加数的个数   
  3.     int  r = 0, j = 0;  
  4.     va_list  pvar;  
  5.     va_start(pvar, i);  
  6.     for (j=0;j<i;j++)  
  7.     {  
  8.         r += va_arg(pvar, int );  
  9.     }  
  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 plain copy to clipboard print ?
  1. #include <stdio.h>            /* 我没包含 stdarg.h 或 vararg.h */   
  2. void  print ( char  * fmt, ...)  
  3. {  
  4.     char  * arg;                /* 变长参数的指针  
  5.                                        相当于 va_list arg */   
  6.     int  i;                     /* 接受int参数 */   
  7.     double  d;                  /* 接受double参数 */   
  8.     char  c;                    /* 接受char 参数*/   
  9.     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 在此对作者表示由衷的感谢

你可能感兴趣的:(C语言中不用宏实现变长参数函数的原理及实现)