本文旨在透過閱讀 Microsoft Visual C++ 9.0 (VC9) 的 C 執行階段庫 (CRT) 原始碼,深入理解 assert.h 中斷言宏的實現細節。分析原則為:從標準庫實現追蹤至 Windows API,API 進入核心層則不再深入;對 C 語法標準的疑問,回溯至 ANSI C 標準文件。
#include <crtdefs.h>
#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 /* NDEBUG */
((void)0),編譯器直接優化移除,零開銷。!!(_Expression):將任意表達式規範化為 0 或 1,確保邏輯判斷正確。|| 運算子特性,條件為真時直接跳過 _wassert 呼叫;為假時執行斷言失敗處理。#_Expression 將運算式轉為字串字面值,_CRT_WIDE 宏 (定義為 L##String) 再轉為寬字元字串,配合 Unicode 版 _wassert。__FILE__ 與 __LINE__ 的標準地位根據 ANSI C 標準 (ISO/IEC 9899:1990) 第 3.8.8 節,編譯器必須預定義 __LINE__ (當前行號,十進位常數) 和 __FILE__ (推定原始檔名,字串字面值)。它們在預處理階段展開,屬於 C 語言標準強制要求的編譯器內建宏。
_wassert 位於 assert.c,主要邏輯:
_set_error_mode 判斷錯誤輸出模式:_OUT_TO_STDERR (標準錯誤) 或 _OUT_TO_DEFAULT (視應用程式類型而定)。STD_ERROR_HANDLE,使用 swprintf 格式化訊息,再呼叫 WriteConsoleW 輸出至主控台,最後呼叫 abort() 終止程式。__crtMessageBox 彈出訊息方塊 (MessageBoxW)。原始碼顯示 __crtMessageBox 使用 LoadLibrary + GetProcAddress 動態取得 MessageBoxW 等 API 位址,而非直接連結。這是為了:
_encode_pointer/_decode_pointer) 防禦惡意覆寫。K&R 《C 程式設計語言》 指出:assert 宏不應直接呼叫 fprintf 等庫函式,因為:
<stdio.h>,導致 stderr、fprintf 未宣告。(assert(expr), other_expr) 這類逗號運算式語境。_wassert 內部直接使用 swprintf、_ftprintf 等函式,屬於「知道自己在做什麼」的實用主義妥協。
面試經典題「實作 strcpy」常見答案使用 assert(strDest && strSrc)。但若 assert 內部實現 (如格式化輸出) 間接呼叫了 strcpy,將導致遞迴無限循環。這提醒我們:標準庫實現必須極度謹慎避免循環依賴,也是為何標準要求 assert 呼叫「秘密名稱」函式的原因。