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

C Primer Plus 學習筆記:可變參數宏(... 與 __VA_ARGS__)與可變參數函數完整解析

     編輯:關於C語言

C Primer Plus 學習筆記:可變參數宏與函數

C99 引入的可變參數宏(Variadic Macros)與可變參數函數(Variadic Functions)是實現類 printf 功能的基石。本文系統整理 __VA_ARGS__、va_list 等核心機制與用法。

一、可變參數宏

基本語法

#define DEBUG(fmt, ...) printf("DEBUG: " fmt, __VA_ARGS__)
  • ...:表示參數列表可變。
  • __VA_ARGS__:預處理階段展開為實際傳入的可變參數序列(含逗號分隔)。

進階示例:自動攜帶文件/行/函數信息

#define DBG(fmt, ...) \
    printf("MSG: " fmt " [File:%s, Line:%d, Func:%s]\n", \
           __VA_ARGS__, __FILE__, __LINE__, __FUNCTION__)

int main() {
    double x = 48, y = sqrt(x);
    DBG("x=%g\n", x);
    DBG("x=%.2f, y=%.4f\n", x, y);
    return 0;
}

輸出:

MSG: x=48.000000 [File:main.c, Line:10, Func:main]
MSG: x=48.00, y=6.9282 [File:main.c, Line:11, Func:main]

不支持 __VA_ARGS__ 的舊編譯器替代方案

#define DEBUG_BUFFER_MAX_LENGTH 1024

void printDebugMsg(const char* format, ...) {
    char buffer[DEBUG_BUFFER_MAX_LENGTH + 1] = {0};
    va_list arg;
    va_start(arg, format);
    vsnprintf(buffer, DEBUG_BUFFER_MAX_LENGTH, format, arg);
    va_end(arg);
    printf("%s", buffer);
}

使用 va_list + vsnprintf 實現類似功能,兼容 C89。

二、可變參數函數

標準協議(stdarg.h)

int SqSum(int n1, ...) {
    va_list arg_ptr;
    int nSqsum = 0;
    int n = n1;

    va_start(arg_ptr, n1);           // n1 是最後一個固定參數
    while (n > 1) {
        nSqsum += n * n;
        n = va_arg(arg_ptr, int);    // 獲取下一個 int 參數
    }
    va_end(arg_ptr);
    return nSqsum;
}

int main() {
    // 參數:計算 2^2 + 3^2 + 4^2 + 5^2,以 -1 結束(邏輯自定義)
    int sum = SqSum(2, 3, 4, 5, -1);  
    printf("sqsum = %d\n", sum);  // 54
    return 0;
}

關鍵宏詳解

宏作用
va_list定義指向可變參數列表的指針對象
va_start(ap, last_fixed)初始化 ap 指向第一個可變參數,last_fixed 為最後一個固定參數名
va_arg(ap, type)返回 ap 當前指向參數(類型 type),並推進 ap 到下一個
va_copy(dest, src)複製參數列表指針(C99),避免 va_arg 消耗原列表
va_end(ap)清理 ap,匹配每個 va_start/va_copy

重要規則

  1. 至少需要一個固定參數(作為 va_start 的參考點)。
  2. 調用方需自行約定參數個數/類型(如 printf 通過格式字符串,execl 以 NULL 結尾)。
  3. va_arg 類型不匹配導致未定義行為。
  4. 參數在棧上按聲明順序排列,va_arg 按類型大小推進指針。

三、宏 vs 函數:選擇指南

場景推薦理由
調試日志、輕量包裝可變參數宏零開銷、編譯期展開、自動攜帶 __FILE__/__LINE__
複雜邏輯、類型安全、遞歸可變參數函數運行期執行、可調試、參數類型檢查(有限)、支持遞歸
需多次遍歷參數函數 + va_copy宏無法多次遍歷 __VA_ARGS__

四、C++11/14 現代替代方案

// 可變參數模板(類型安全、零開銷)
template
void log(const char* fmt, Args&&... args) {
    // 配合 fmtlib 或 iostream 實現
}

// 折疊表達式(C++17)
template
void print(Args&&... args) {
    (std::cout << ... << args) << '\n';
}

現代 C++ 推薦使用可變參數模板替代 C 風格可變參數,獲得編譯期類型安全與更好性能。

總結

  • __VA_ARGS__ 是 C99 宏系統的關鍵擴展,配合 ## 可解決零參數時的多餘逗號問題(##__VA_ARGS__)。
  • va_list 系列宏是 C 標準庫實現 printf/scanf 族函數的基石。
  • 理解參數在棧上的佈局與 va_arg 推進機制,有助於排查棧損壞類 Bug。
  • 新代碼優先考慮 C++ 可變參數模板或 fmt::format(C++20 std::format)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved