首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

C語言可變參數詳解:從 va_list 到手寫 printf 完整實作

     編輯:關於C

可變參數機制核心

C 語言透過 `` 提供四個巨集支援不定參數函式:

巨集說明
va_list ap宣告參數列表遍歷器
va_start(ap, last_named_param)初始化 `ap` 指向第一個可變參數
va_arg(ap, type)取得目前參數值並推進 `ap`
va_end(ap)結束遍歷,清理資源

應用範例一:計算任意個數平均值

double average(int count, ...) {
    va_list ap;
    va_start(ap, count);
    double sum = 0;
    for (int i = 0; i < count; i++)
        sum += va_arg(ap, double); // 參數預設提升為 double
    va_end(ap);
    return sum / count;
}

應用範例二:尋找多個數中的最大值

int max_of(int count, ...) {
    va_list ap;
    va_start(ap, count);
    int max = va_arg(ap, int); // 至少有一個參數
    for (int i = 1; i < count; i++) {
        int val = va_arg(ap, int);
        if (val > max) max = val;
    }
    va_end(ap);
    return max;
}

核心挑戰:模擬 printf 格式化輸出

格式字串解析邏輯

  1. 掃描字串,遇到 `%` 進入格式解析狀態。
  2. 解析標誌、寬度、精度、長度修飾符、轉換規格。
  3. 根據轉換規格從 `va_arg` 取出對應型別參數。
  4. 將數值轉為字串輸出(整數需處理負數、進位)。

完整 my_printf 實作(支援 %d, %c, %s, %%)

#include 
#include 

// 遞迴輸出整數
void print_int(int n) {
    if (n < 0) { putchar('-'); n = -n; }
    if (n >= 10) print_int(n / 10);
    putchar('0' + n % 10);
}

void my_printf(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);

    while (*fmt) {
        if (*fmt == '%') {
            fmt++;
            switch (*fmt) {
                case 'd': {
                    int val = va_arg(ap, int);
                    print_int(val);
                    break;
                }
                case 'c': {
                    int val = va_arg(ap, int); // char 提升為 int
                    putchar(val);
                    break;
                }
                case 's': {
                    char *s = va_arg(ap, char *);
                    if (!s) s = "(null)";
                    while (*s) putchar(*s++);
                    break;
                }
                case '%':
                    putchar('%');
                    break;
                default:
                    putchar('%'); putchar(*fmt); // 未知格式原樣輸出
            }
        } else {
            putchar(*fmt);
        }
        fmt++;
    }
    va_end(ap);
}

int main() {
    my_printf("Hello %s! Num=%d, Char=%c, %%\n", "World", 42, 'X');
    return 0;
}

進階擴展:支援寬度、精度、長度修飾符

// 解析 %[flags][width][.precision][length]specifier
// 範例:%08lx -> flags='0', width=8, length='l', specifier='x'
// 需建立狀態機解析格式字串,再根據 length 選擇 va_arg 型別:
//   hh -> char, h -> short, l -> long, ll -> long long, z -> size_t, t -> ptrdiff_t

常見錯誤與最佳實踐

  1. 型別不匹配:`%d` 對應 `int`,`%ld` 對應 `long`,`%lld` 對應 `long long`,`%f` 對應 `double`(float 提升)。錯誤導致未定義行為。
  2. 缺少 `va_end`:某些 ABI 會導致堆疊損壞,務必配對呼叫。
  3. 重複使用 `va_list`:需 `va_copy(dest, src)` 複製,或重新 `va_start`。
  4. 空指標 `%s`:標準 `printf` 輸出 `(null)`,手寫版需自行檢查。
  5. 浮點數轉字串:極其複雜,實務上直接呼叫 `sprintf` 或 `dtoa`(Dragon4/Grisu3 演算法)。

總結

可變參數是 C 語言實現 `printf`、`scanf`、`execl` 等函式的基石。掌握 `va_list` 四巨集操作、預設參數提升規則、格式字串解析邏輯,不僅能手寫格式化函式,更能深入理解 C 呼叫慣例與堆疊幀佈局,為除錯、逆向工程、編譯器開發打下堅實基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved