天天看點

c語言不定參數探究

    最近,遇到一個c語言的不定參數問題。其實,對于c語言的不定參數問題,隻需要三個函數就可以搞定了。這三個函數的頭檔案是<stdarg.h>,其實下面的三個函數都是一個宏定義(macro)。    這三個函數是:

    void va_start(va_list ap, last);

    type va_arg(va_list ap, type);

    void va_end(va_list ap);

    如果需要進行其他的一些操作,可以檢視一下man手冊進行查詢。

    在這三個函數解釋之前,先看一個變量va_list,這個變量的類型是什麼呢?通過檢視核心源代碼,一直追蹤下去,才發現它的類型是void *類型的。

    對于va_start(va_list ap, last)函數,這個函數是用來初始化指針變量ap(va_list類型)的,以後處理參數就是預設從ap處開始處理。last一般為char *傳過來參數清單的第一個參數。

    對于va_arg(va_list ap, type)函數來說,就是将ap指針按照type類型向後移動,然後取出ap指針所指的那個參數。

    對于va_end(va_list ap)一般和va_start(va_list ap, last)配套使用,做一些善後處理的事情。

    這裡有一個問題,當我們取參數的時候,如何判斷我們要取的參數已經取完了?開始我是這麼想的,通過va_arg的傳回值進行判斷,通過查閱資料,都是這麼說的,看來我的猜想是對的。當我把程式寫出來進行測試的時候,發現不是這樣的:

  1. #include <stdio.h>
  2. #include <stdarg.h>
  3. int sum(const int , ...);
  4. int main(void)
  5. {
  6.     printf("The result is:%d\n", sum(10, 9, 8));
  7.     return 0;
  8. }
  9. int sum(const int first, ...)
  10.     va_list argp;
  11.     int sum = 0;
  12.     int tmp;
  13.     va_start(argp, first);
  14.     sum += first;
  15.     printf("%d\n", first);
  16.     while((tmp = va_arg(argp, int)) != 0) {
  17.         printf("%d\n", tmp);
  18.         sum += tmp;
  19.     }
  20.     va_end(argp);
  21.     return sum;

這個程式的運作結果是:

10

9

8

6676468

134513824

The result is:141190319

    這個結果說明,通過va_arg的傳回值進行參數是否取完來判斷是有問題的。

    會不會是通過argp的值來判斷的呢?讓我們來做個測試:

  1.     while(argp) {
  2.         tmp = va_arg(argp, int);

    這個程式的執行結果出乎我的意料,出現了段錯誤。

    至于如何修改這個程式把不定參數取出來,我還是沒有找到解決方法。後來,我想到了printf()函數,我檢視了它的源代碼,其中主要是調用了vsprintf()函數,至于為什麼調用vsprintf()函數,我想可能是為了實作類似于fprintf()之類的函數調用的友善,這樣也提高了函數的使用率。printf()函數的主要代碼:

  1. 328 va_start(args, fmt);
  2. 329 n = vsprintf(sprint_buf, fmt, args);
  3. 330 va_end(args);

    我繼續檢視了vsprintf()函數,結果發現,在這個函數當中,它好像是通過判斷字元串當中“%”号的多少來決定後面參數的個數的。想到這裡,我斷定,在想調用不定參數這樣的函數的時候,其實是需要指出參數的個數的,隻是是通過間接的方式。比如我們最熟悉的printf()函數,其實我們在第一個參數當中,通過%号已經指出了參數的個數,不是嗎?

    想到這裡,我想到了之前看到man手冊中給出的例子為什麼是這樣的:

  1.        #include <stdarg.h>
  2.        void
  3.        foo(char *fmt, ...)
  4.        {
  5.            va_list ap;
  6.            int d;
  7.            char c, *s;
  8.            va_start(ap, fmt);
  9.            while (*fmt)
  10.                switch (*fmt++) {
  11.                case 's': /* string */
  12.                    s = va_arg(ap, char *);
  13.                    printf("string %s\n", s);
  14.                    break;
  15.                case 'd': /* int */
  16.                    d = va_arg(ap, int);
  17.                    printf("int %d\n", d);
  18.                case 'c': /* char */
  19.                    /* need a cast here since va_arg only
  20.                       takes fully promoted types */
  21.                    c = (char) va_arg(ap, int);
  22.                    printf("char %c\n", c);
  23.                }
  24.            va_end(ap);
  25.        }

    這裡的話,不是就通過第一個參數指定之後才讀取的嗎?其實我覺得是間接的告訴了參數的個數。

    通過上面的分析,下面做了一個簡單的不定參數的應用。

    問題描述:給定一些字元串,求出它們的最長開始字串。

    實驗代碼如下:

  1. #include <stdlib.h>
  2. #include <string.h>
  3. void fun(char *fmt, ...);
  4. int main()
  5.     fun("sss", "fanabcd", "fanfanfanfan", "fanyyyyyyyyyyyy");
  6.     return 0;    
  7. void fun(char *fmt, ...)
  8.     char * str, res[20] = {0};
  9.     int i;
  10.     va_start(argp, fmt);
  11.     if(*fmt == 's') {
  12.         str = va_arg(argp, char *);
  13.         strcpy(res, str);
  14.     fmt++;
  15.     while(*fmt) {
  16.         if(*fmt++ == 's') {
  17.             str = va_arg(argp, char *);
  18.             i = 0;
  19.             while(res[i] != '\0') {
  20.                 if(res[i] != str[i]) {
  21.                     res[i] = 0;
  22.                     break;
  23.                 }
  24.                 i++;
  25.             }
  26.         }
  27.     printf("The result is:%s\n", res);

    程式的執行結果是:

The result is:fan

    通過這樣的折騰,就把c語言的不定參數簡單地應用起來了。