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

C 標準庫源碼深度解讀(VC9.0):assert.h 斷言機制、_wassert 實現與 Windows API 交互

     編輯:關於C

assert.h 核心源碼(VC9.0 CRT)


#include 

#undef assert

#ifdef NDEBUG
#define assert(_Expression) ((void)0)
#else
#ifdef __cplusplus
extern "C" {
#endif

_CRTIMP void __cdecl _wassert(
    _In_z_ const wchar_t *_Message,
    _In_z_ const wchar_t *_File,
    _In_ unsigned _Line
);

#ifdef __cplusplus
}
#endif

#define assert(_Expression) \
    (void)( (!!(_Expression)) || \
            (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )

#endif

關鍵機制解析

1. NDEBUG 控制編譯

  • Release 模式:定義 NDEBUG,assert 展開為 ((void)0),編譯器優化掉,零開銷。
  • Debug 模式:未定義 NDEBUG,展開為運行時檢查邏輯。

2. 斷言宏展開邏輯


(void)( (!!(_Expression)) || (_wassert(...), 0) )
  • !!(_Expression):雙重邏輯非,將任意標量轉為標準布爾值(0 或 1)。
  • 短路求值:若表達式為真(1),|| 右側不執行,斷言通過。
  • 若為假(0),執行 _wassert(...), 0:調用報告函數,逗號表達式整體值為 0,外層 (void) 消除值。
  • 整個宏為 void 表達式,可用於任意語句位置(如 if (assert(p)) ...)。

3. _CRT_WIDE 宏:字符串寬字符化


#ifndef _CRT_WIDE
#define __CRT_WIDE(_String) L ## _String
#define _CRT_WIDE(_String) __CRT_WIDE(_String)
#endif
  • 兩層展開:_CRT_WIDE(#_Expression) → __CRT_WIDE("expr") → L"expr"。
  • 確保傳給 _wassert 的字符串為寬字符(wchar_t*),匹配 Windows Unicode API。

4. __FILE__ 和 __LINE__:編譯器內置宏

  • ANSI C 標準要求編譯器預定義:__FILE__ (當前文件名字符串)、__LINE__ (當前行號十進制常量)。
  • 預處理階段展開,而非運行時計算。

_wassert 實現源碼 (assert.c)


#ifdef _UNICODE
void __cdecl _wassert(const wchar_t *expr, const wchar_t *file, unsigned line)
#else
void __cdecl _assert(const char *expr, const char *file, unsigned line)
#endif
{
    // 1. 確定輸出目標:stderr (控制台) 或 MessageBox (GUI)
    int err_mode = _set_error_mode(_REPORT_ERRMODE);
    int app_type = __app_type; // _CONSOLE_APP / _GUI_APP

    if ((err_mode == _OUT_TO_STDERR) || 
        (err_mode == _OUT_TO_DEFAULT && app_type == _CONSOLE_APP)) {
        
        // 控制台輸出路徑
        if (!anybuf(stderr)) 
            setvbuf(stderr, NULL, _IONBF, 0); // 無緩衝,立即顯示
        
        _ftprintf(stderr, _assertstring, expr, file, line);
        fflush(stderr);
    } else {
        // GUI 應用:彈出 MessageBox
        // 構建寬字符消息緩衝區
        wchar_t assertbuf[ASSERTBUFSZ];
        swprintf(assertbuf, ASSERTBUFSZ, _assertstring, expr, file, line);
        
        // 獲取活動窗口句柄作為父窗口
        HWND hwnd = GetActiveWindow();
        if (hwnd) hwnd = GetLastActivePopup(hwnd);
        
        // 服務/非交互進程檢測
        HWINSTA hwinsta = GetProcessWindowStation();
        USEROBJECTFLAGS uof;
        if (hwinsta && GetUserObjectInformation(hwinsta, UOI_FLAGS, &uof, sizeof(uof), NULL)) {
            if (!(uof.dwFlags & WSF_VISIBLE))
                uType |= MB_SERVICE_NOTIFICATION;
        }
        
        // 動態加載 USER32.DLL 獲取 MessageBox 函數指針
        // (避免靜態鏈接依賴,確保最小運行時體積)
        int nCode = __crtMessageBox(assertbuf, 
            L"Microsoft Visual C++ Runtime Library",
            MB_ABORTRETRYIGNORE | MB_ICONHAND | MB_SETFOREGROUND | MB_TASKMODAL);
        
        // 處理用戶選擇:Abort→abort(), Retry→DebugBreak(), Ignore→繼續
        if (nCode == IDABORT) abort();
    }
}

__crtMessageBox 動態加載 USER32.DLL 設計解析


int __cdecl __crtMessageBoxW(LPCTSTR text, LPCTSTR caption, UINT type) {
    typedef int (WINAPI *PFNMessageBox)(HWND, LPCTSTR, LPCTSTR, UINT);
    static PFNMessageBox pfnMessageBox = NULL;
    static PFNGetActiveWindow pfnGetActiveWindow = NULL;
    // ... 其他函數指針 ...

    if (!pfnMessageBox) {
        HMODULE hlib = LoadLibrary(_T("USER32.DLL"));
        pfnMessageBox = (PFNMessageBox)GetProcAddress(hlib, "MessageBoxW");
        pfnGetActiveWindow = (PFNGetActiveWindow)GetProcAddress(hlib, "GetActiveWindow");
        // ... 緩存函數指針 ...
    }
    // 編碼指針防護 (_encode_pointer/_decode_pointer) 防止指針劫持
    return pfnMessageBox(hwnd, text, caption, type);
}

為什麼動態加載而非靜態鏈接?

  1. 最小運行時體積:CRT 不強制依賴 USER32.DLL,控制台程序可不加載 GUI 庫。
  2. 版本兼容:通過 GetProcAddress 適配不同 Windows 版本的 API 簽名變化。
  3. 安全性:_encode_pointer 混淆函數指針,緩解 ROP 攻擊。
  4. 延遲綁定:僅在斷言失敗(極低頻事件)時加載,節省啟動時間。

標準庫設計藝術:K&R《C程序設計語言》P20 斷言宏規範

1. 斷言宏不得直接調用庫輸出函數(如 fprintf),因其可能未包含 。
2. 不得引用 stderr 宏。
3. 必須展開為 void 表達式。
4. 必須使用條件運算符而非 if,以便用於表達式中。
5. 應調用內部祕密函數(如 _wassert)輸出錯誤信息。
6. 應生成高效緊湊代碼。

VC CRT 通過 _wassert 內部函數滿足規範 5,但該函數內部直接調用 _ftprintf/MessageBox 違反規範 1(實用主義妥協)。

總結:從 assert.h 窺見 CRT 設計哲學

  1. 零開銷抽象:Release 完全編譯掉斷言。
  2. 多目標適配:控制台/GUI/服務自動選擇輸出方式。
  3. 安全防護:指針編碼、動態加載、無緩衝輸出。
  4. 標準合規與實用平衡:盡量符合 K&R 規範,關鍵處為實用妥協。
  5. 寬字符優先:內部統一 Unicode,_CRT_WIDE 統一轉換入口。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved