在C語言開發中,可變參數(va_list、va_start、va_arg、va_end)是處理參數數量和類型不固定函數的標準機制。大多數開發者熟悉其基本用法:在函數內部提取參數值供讀取使用。然而,va_arg還有一種鮮為人知但極具靈活性的用法——將其作為左值進行賦值操作,從而實現對調用者傳入變量的直接修改。
傳統模式下,va_arg出現在賦值表達式的右側:
void func(Type para1, Type para2, Type para3, ...) {
va_list ap;
va_start(ap, para3); // 必須是「...」之前的固定參數
Type val = va_arg(ap, Type); // 提取值
// 繼續提取其他參數...
va_end(ap);
}
考慮以下代碼片段:
void PassInto(int ID, ...) {
va_list valist;
va_start(valist, ID);
*(uint32_t*)va_arg(valist, uint32_t*) = 10;
va_end(valist);
}
// 調用示例
uint32_t value = 0;
PassInto(id, &value);
printf("%d", value); // 輸出 10
這裡 va_arg(valist, uint32_t*) 取出的是指向 value 的指針(即 &value),通過強制類型轉換並解引用,直接修改了調用者的局部變量。這在實現「輸出參數」或通用數據填充接口時非常方便。
C語言函數參數傳遞本質上是值傳遞。調用者傳入 &value(指針值),被調用者取出該指針並解引用寫入數據,相當於在調用者棧幀中直接操作內存。這與 scanf("%d", &value) 的原理一致。
這種技巧極其依賴類型匹配。若調用者傳入 uint16_t* 而函數按 uint32_t* 解引用寫入:
uint16_t value = 0;
PassInto(id, &value); // 危險!實際只分配了 2 字節,卻寫入 4 字節
後果是高 16 位會覆蓋 value 之後的內存區域,可能破壞其他變量、破壞棧幀結構,導致程序崩潰或難以復現的隨機錯誤。編譯器通常不會對 va_arg 的類型參數進行檢查,錯誤往往只在運行時暴露。
assert(sizeof(*ptr) == expected_size))。總結:va_arg 左值用法雖然強大,但屬於「鋒利的刀具」,需在完全掌控類型契約的前提下謹慎使用,否則極易引入難以診斷的內存破壞缺陷。