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

C 語言可變參數實現原理與自製 PrintInt 函數詳解

     編輯:關於C語言

printf、scanf 等函數支援不定數量參數,靠的是 C 語言可變參數 (Variadic Functions)機制。本文深入剖析堆疊傳參原理,手把手實現一個簡易的 PrintInt 函數,解析 %d 格式化處理流程。

可變參數核心原理:堆疊佈局

C 語言參數從右向左壓入堆疊:

高地址
+----------------+
| 參數 3         |  ← 最後壓入,最高地址
+----------------+
| 參數 2         |
+----------------+
| 參數 1         |  ← 最先壓入,最低地址
+----------------+
| 返回地址       |
+----------------+
| 舊 EBP         |
+----------------+
低地址

固定參數 (如格式字串) 位於已知位置,後續可變參數在堆疊中連續分佈。只要知道第一個可變參數地址,按格式字串解析即可依次取值。

標準庫 <stdarg.h> 宏 (現代標準做法)

#include <stdarg.h>

void print_ints(int count, ...) {
    va_list args;
    va_start(args, count);  // 以最後一個固定參數為基準
    for (int i = 0; i < count; i++)
        printf("%d ", va_arg(args, int));  // 依次取 int 參數
    va_end(args);
    printf("\n");
}

這是正規、可移植的做法,編譯器處理架構差異 (x86/x64/ARM 參數傳遞不同)。

教學版:手工解析堆疊實現 PrintInt (僅限 x86 cdecl)

⚠️ 僅供原理教學,生產代碼嚴禁使用!

void PrintInt(char* buffer, int data, ...) {
    static char space[1024];
    char temp[32];
    int* start = (int*)&buffer;  // 危險!假設堆疊佈局
    int count = 0;

    if (!buffer) return;
    memset(space, 0, 1024);
    memset(temp, 0, 32);

    // 掃描格式字串
    while (buffer[count]) {
        if (!strncmp(&buffer[count], "%d", 2)) {
            start++;  // 移至下一個堆疊位置 (假設 int 大小)
            itoa(*start, temp, 10);  // 整數轉字串
            strcat(space, temp);
            count += 2;
            continue;
        }
        space[strlen(space)] = buffer[count++];  // 普通字符複製
    }
    memset(buffer, 0, strlen(buffer));
    memmove(buffer, space, strlen(space));
}

測試代碼

void display() {
    char buffer[32] = "%d %d %d %d\n";
    PrintInt(buffer, 1, 2, 3, 4);  // 格式字串 + 4 個 int
    printf(buffer);  // 輸出: 1 2 3 4
}

手工實現的嚴重缺陷

正確的現代實現方式

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

void PrintIntSafe(char* buffer, size_t buf_size, const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vsnprintf(buffer, buf_size, fmt, args);  // 安全格式化
    va_end(args);
}

或 C++11 起使用 std::format (C++20) / fmt::format (第三方庫)。

總結

  • 可變參數本質:堆疊連續佈局 + 格式字串解析驅動取值
  • 必須使用 <stdarg.h> 宏:va_list, va_start, va_arg, va_end
  • 手工解析堆疊不可移植、極度危險,僅供教學理解原理
  • 現代 C++ 應優先使用型別安全的格式化庫 (fmt, std::format)

理解可變參數原理,有助於除錯格式化字串漏洞、實現日誌庫、掌握呼叫慣例細節。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
問題後果
假設參數在堆疊連續x64 呼叫慣例前 4 參數用寄存器 (RCX, RDX, R8, R9),不在堆疊
假設 int 大小固定不同架構 int/指標大小不同
(int*)&buffer 取址錯誤取的是 buffer 區域變數地址,非參數堆疊位置
無型別安全%d 對應非 int 參數未定義行為
緩衝區溢出風險strcat 未檢查邊界