#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
NDEBUG,assert 展開為 ((void)0),編譯器優化掉,零開銷。NDEBUG,展開為運行時檢查邏輯。
(void)( (!!(_Expression)) || (_wassert(...), 0) )
!!(_Expression):雙重邏輯非,將任意標量轉為標準布爾值(0 或 1)。|| 右側不執行,斷言通過。_wassert(...), 0:調用報告函數,逗號表達式整體值為 0,外層 (void) 消除值。void 表達式,可用於任意語句位置(如 if (assert(p)) ...)。
#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。__FILE__ (當前文件名字符串)、__LINE__ (當前行號十進制常量)。
#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();
}
}
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);
}
GetProcAddress 適配不同 Windows 版本的 API 簽名變化。_encode_pointer 混淆函數指針,緩解 ROP 攻擊。1. 斷言宏不得直接調用庫輸出函數(如 fprintf),因其可能未包含。
2. 不得引用stderr宏。
3. 必須展開為void表達式。
4. 必須使用條件運算符而非if,以便用於表達式中。
5. 應調用內部祕密函數(如_wassert)輸出錯誤信息。
6. 應生成高效緊湊代碼。
VC CRT 通過 _wassert 內部函數滿足規範 5,但該函數內部直接調用 _ftprintf/MessageBox 違反規範 1(實用主義妥協)。
_CRT_WIDE 統一轉換入口。