每個 C 程式設計師的第一行代碼往往是 printf("Hello World\\n");。但你是否真正理解 printf 如何解析格式字串、提取可變參數、轉換數值、緩衝輸出?本文帶你閱讀 glibc 原始碼,追蹤 printf 完整執行路徑,並分享一個跨平台浮點數輸出失效的真實踩坑案例。
vfprintf、解鎖、返回寫入字元數。_stbuf,4096 位元組)、核心解析邏輯 _output_l、刷新緩衝 (_ftbuf → flush)。ST_NORMAL:普通字元直接寫入緩衝。ST_PERCENT:遇到 % 進入格式解析,提取旗標、寬度、精度、長度修飾、轉換類型。ST_TYPE:根據類型 (d, f, s...) 從 va_list 提取引數,呼叫轉換函數 (如 _cvt、浮點用 dtoa) 產生字串存入緩衝。va_list (實為 char*)、va_start(ap, fmt) 定位首參數位址、va_arg 依類型取值、va_end 歸零防野指標。/* printf.c */
int printf(const char *format, ...) {
va_list ap; va_start(ap, format);
int retval = vfprintf(stdout, format, ap);
va_end(ap); return retval;
}
/* vfprintf.c -> _output_l 核心循環 */
while (*format) {
ch = *format++;
switch (state) {
case ST_NORMAL: if (ch != '%') put_to_buffer(ch); else state = ST_PERCENT; break;
case ST_PERCENT: parse_flags_width_precision(); state = ST_TYPE; break;
case ST_TYPE: extract_arg_and_convert(ch); state = ST_NORMAL; break;
}
}
移植代碼到 ARM 裸機環境,printf("%f", 3.14) 輸出 0.000000,整數輸出正常。
提問被刁難「參數寫錯」、「垃圾問題」,修正參數後仍無人給出正確方向。最終兩位資深嵌入式工程師給出關鍵洞見:
va_arg 取 double (8 位元組) 時讀取錯誤位址 → 浮點數解析失敗。解決:調整啟動代碼/線程棧對齊屬性。-u _printf_float 或對應宏定義。經驗證,原因 1 為我們專案的真兇。調整棧對齊後浮點數完美輸出。
printf 實作教學。vsnprintf 是理解格式解析的最佳練習。下次再寫 printf,腦海中浮現的不再是黑盒,而是狀態機跳轉、va_list 指針運算、緩衝區刷新的完整畫面。這才是真正「懂」 printf。