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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章