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

微軟 CRT 源碼剖析:output.c 核心輸出邏輯完整解讀

     編輯:關於C

背景

文章貼出 Microsoft C Runtime (CRT) 中 output.c 完整源碼(版權屬微軟),這是 printf 族函數(printf、fprintf、sprintf、vprintf 等)的核心實現,採用**有限狀態機 (FSM)** 解析格式字串,逐字符處理,支援 ANSI C、Unicode、多執行緒、多種平台(Win32、POSIX、Mac、Alpha)。

核心架構:狀態機解析格式字串

狀態定義

enum STATE {
    ST_NORMAL,   // 普通字符,直接輸出
    ST_PERCENT,  // 讀到 '%'
    ST_FLAG,     // 旗標:- + 空格 # 0
    ST_WIDTH,    // 欄位寬度
    ST_DOT,      // 讀到 '.'
    ST_PRECIS,   // 精度
    ST_SIZE,     // 大小修飾:h l L w 等
    ST_TYPE      // 類型指示符:d i u x X o c s p n e f g 等
};

字符分類查表法

  • 預先生成 __lookuptable[](由 maketabc.c 產出),每個字節高 4 位存**下一狀態**,低 4 位存**字符類別**。
  • O(1) 查表決定狀態轉移,無需複雜 if-else 鏈。

關鍵數據結構

union {
    char *sz;        // 多位元組字串指標
    wchar_t *wz;     // 寬字符字串指標
} text;

union {
    char sz[BUFFERSIZE];          // 轉換緩衝區 (512 bytes)
    wchar_t wz[BUFFERSIZE];       // 寬字符版本
} buffer;
  • BUFFERSIZE = 512 滿足 ANSI C 最小 509 字元要求,並容納最大浮點數轉換結果。
  • bufferiswide 標記緩衝區當前內容類型。

核心流程:_output() / _woutput()

  1. 初始化旗標、寬度、精度、前綴、基數等變數。
  2. 主迴圈:逐字符讀取格式字串 → 查表得類別 → 查表得下一狀態 → switch(state) 執行對應動作。
  3. 狀態 ST_TYPE 時,進入巨大 switch(ch) 分派具體類型轉換:
      - 整數:COMMON_INT 統一處理 d/i/u/o/x/X/p,支援基數 8/10/16、旗標、精度、前綴。
      - 浮點數:呼叫 _cfltcvt / _cldcvt,處理 e/f/g/E/G,支援大小寫指數、# 旗標強制小數點、g 格式裁剪尾隨零。
      - 字符/字串:c/C/s/S/Z,處理多位元組⇄寬字符轉換、NULL 指標顯示 (null)、精度截斷。
      - %n:將已輸出字符數寫回對應指標,不產生輸出。
  4. 轉換完成後,統一處理對齊、填充、前綴、實際輸出(WRITE_CHAR/WRITE_STRING/WRITE_MULTI_CHAR)。
  5. 返回總輸出字符數,或 -1 表示 I/O 錯誤。

多平台/字符集適配細節

輸出輔助函數

LOCAL(void) write_char(int ch, FILE *f, int *pnumwritten);      // 單字符
LOCAL(void) write_multi_char(int ch, int num, FILE *f, int *pnw); // 重複字符
LOCAL(void) write_string(char *s, int len, FILE *f, int *pnw);   // 字串
// 以上均有 WPRFLAG/CPRFLAG 變體
  • 全部為 static _CALLTYPE4(LOCAL 宏),內聯、無鎖(註明 **** DOESN'T CURRENTLY DO MTHREAD LOCKING ****)。
  • 錯誤時將 *pnumwritten = -1,主迴圈檢測到 < 0 即中斷。

參數提取宏

__inline int get_int_arg(va_list *pargptr) { return va_arg(*pargptr, int); }
#if !LONG_IS_INT
__inline long get_long_arg(va_list *pargptr) { return va_arg(*pargptr, long); }
#endif
#if !SHORT_IS_INT
__inline short get_short_arg(va_list *pargptr) { return va_arg(*pargptr, short); }
#endif
#ifdef _ALPHA_
__inline __int64 get_quad_arg(va_list *pargptr) { return va_arg(*pargptr, __int64); }
#endif

根據目標平台類型大小(LONG_IS_INT、PTR_IS_INT 等宏)自動選擇正確提取方式。

歷史修訂紀錄亮點

  • 1989-1992 年持續維護,支援從 16-bit DOS 到 Win32、Alpha、Unicode、MBCS、POSIX。
  • 早期處理 va_arg 對 double 不一致問題(1991-12-05)。
  • 逐步加入 %Z (counted string)、%wc/%ws、%tc/%ts 等擴展格式。

學習價值

  1. **工業級 FSM 解析器** 範本:查表驅動、狀態清晰、擴展性強。
  2. **可移植性設計**:平台差異集中在宏與查表,核心邏輯不變。
  3. **邊界處理**:精度預設、旗標衝突、NULL 指標、緩衝區溢出防護、多位元組/寬字符轉換錯誤處理。
  4. **性能優化**:整數轉換就地反向寫入緩衝區末尾,避免額外反轉;浮點數委託專用轉換器。

註:源碼版權屬 Microsoft,僅供學習研究參考。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
宏定義用途
WPRFLAG寬字符版本 (wprintf 族)
CPRFLAG控制台直接輸出 (_cprintf)
_UNICODE / _MBCS字符集模式
_WIN32_ / _POSIX_ / _CRUISER_ / _MAC_目標平台,決定查表內容
_ALPHA_DEC Alpha 64-bit 整數支援 (__int64 / FL_LONGDOUBLE)