printf("format", args...)
│
├─→ va_start/va_arg/va_end 可變參數提取
│
├─→ vsprintf(buffer, format, ap) 格式化 → 字串
│ │
│ ├─ 解析 format:普通字符直接拷貝
│ ├─ 遇到 %:解析標志、寬度、精度、長度、類型
│ ├─ va_arg 提取對應類型參數
│ ├─ 數值 → 字串轉換(itoa/dtoa 等)
│ └─ 寫入緩衝區
│
└─→ write(fd, buffer, len) 系統調用輸出
│
└─→ 內核 sys_write → 驅動程序 → 硬件(終端/串口/桌面)
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) (ap = (va_list)0)
last 是最後一個固定參數,其地址 + 大小即為第一個可變參數地址。format 正確填寫 va_arg(ap, type),編譯器不檢查。va_copy 允許複制 va_list 多次遍歷。vsprintf / vfprintf狀態機解析 format:
ST_NORMAL → 普通字符,直接輸出
ST_PERCENT → 遇到 %,進入格式解析
ST_FLAG → 解析標志:- + 0 # 空格
ST_WIDTH → 解析寬度:* 或數字
ST_PRECIS → 解析精度:.數字 或 .*
ST_LENGTH → 解析長度:hh h l ll j z t L
ST_TYPE → 解析類型:d i o u x X f F e E g G a A c s p n
每個類型對應一個處理函數,提取參數 → 轉字串 → 寫緩衝區。
_dl_float_to_string。0x%lx(大端序)。FILE 結構體
struct _IO_FILE {
char *_IO_buf_base; // 緩衝區起始
char *_IO_buf_end; // 緩衝區末尾
char *_IO_write_ptr; // 當前寫位置
char *_IO_write_end; // 緩衝區可寫末尾
// ... 鎖、檔案描述符、標志等
};
\n 刷新(終端預設)。write(stderr 預設)。fflush / fclose / 程序正常退出會觸發刷新。write → sys_write
// 用戶態 glibc write()
ssize_t write(int fd, const void *buf, size_t count) {
return syscall(SYS_write, fd, buf, count);
}
// 內核態 sys_write()
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count) {
struct fd f = fdget_pos(fd);
if (!f.file) return -EBADF;
ssize_t ret = vfs_write(f.file, buf, count, &f.file->f_pos);
fdput_pos(f);
return ret;
}
最終到達具體文件系統的 write_iter 或設備驅動的 write 回調(如 tty_write、console_write)。
| 場景 | Linux/glibc | Windows/MSVC | 嵌入式 newlib |
|---|---|---|---|
| 浮點數格式化 | Ryu (glibc 2.32+) | 自研 | 簡易 dtoa |
| %n 支持 | ✓ | ✓ (需啟用) | 常裁剪 |
| 位寬類型 %zu | C99 標準 | C99 部分支持 | 視配置 |
| 線程安全 | FILE 內部鎖 | FILE 內部鎖 | 常無鎖 |
-u _printf_float 或勾選 IDE 選項)。vsprintf 遞歸/大數組導致棧溢出。stdout 未正確綁定輸出設備(如 UART)。printf 看似簡單,實則串聯了可變參數提取、格式字串狀態機解析、高精度數值轉字串、多層緩衝管理、系統調用接口、設備驅動等核心系統軟體技術。理解其內部鏈路,對排查輸出異常、優化嵌入式體積、實現自定義日誌庫極有幫助。