// 1. 用戶調用
int printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = vfprintf(stdout, fmt, ap); // 核心格式化邏輯
va_end(ap);
return ret;
}
// 2. vfprintf 核心 (簡化)
int vfprintf(FILE *fp, const char *fmt, va_list ap) {
// 解析 fmt,提取標誌/寬度/精度/長度/轉換符
// 對每個轉換符:
// 從 ap 取參數 (va_arg)
// 格式化為字串 (數字→字串、指標→%p、字串→直接複製)
// 寫入 fp->_IO_buf_base 緩衝區
// 刷新緩衝區 (滿/行緩衝遇\n/強制)
return total_written;
}
// 3. 底層寫入
ssize_t write(int fd, const void *buf, size_t count); // 系統調用
// stdout 通常 fd=1,最終經內核輸出到終端/管道/重定向檔案
<stdarg.h>)
typedef char *va_list;
#define va_start(ap, last) (ap = (va_list)&last + sizeof(last)) // 簡化
#define va_arg(ap, type) (*(type*)((ap += sizeof(type)) - sizeof(type)))
#define va_end(ap) (void)0
va_start 定位到首個可變參數棧位置。va_arg 按類型大小遞增指標並解引用。fmt 指示。
%[flags][width][.precision][length]specifier
flags: - + 0 # ' '
width: 數字 或 *
precision: .數字 或 .*
length: hh h l ll j z t L
specifier: d i u o x X f F e E g G a A c s p n %
glibc 內部為每種 specifier 註冊處理函數,支援擴展 (如 register_printf_function GNU 擴展)。
| 模式 | 觸發刷新 | 典型場景 |
|---|---|---|
| 全緩衝 | 緩衝區滿 | 檔案、管道、重定向 |
| 行緩衝 | 遇 \n / 緩衝區滿 / 輸入請求 | 終端 (stdout/stderr) |
| 無緩衝 | 每次調用 | stderr (預設)、setbuf(fp, NULL) |
puts/fputs 無格式化解析,極快,適合固定字串。snprintf 格式化到記憶體,避免 I/O 開銷。setvbuf(stdout, NULL, _IOFBF, 65536) 增大緩衝區減少系統調用。printf(user_input) → 攻擊者控制 %n 寫入任意記憶體 → **永遠用 printf("%s", user_input)**。%n 計算已寫字符數並寫入對應指標,**極度危險**,現代編譯器 -Wformat-security 會警告。