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

C 標準庫原始碼深度解析:assert.h 斷言機制實現 (VC9.0)

     編輯:關於C語言

前言與分析原則

本文旨在透過閱讀 Microsoft Visual C++ 9.0 (VC9) 的 C 執行階段庫 (CRT) 原始碼,深入理解 assert.h 中斷言宏的實現細節。分析原則為:從標準庫實現追蹤至 Windows API,API 進入核心層則不再深入;對 C 語法標準的疑問,回溯至 ANSI C 標準文件。

assert.h 核心原始碼結構

#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 */

關鍵點解析

  • NDEBUG 宏:控制 Release/Debug 版本行為。Release 下展開為 ((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 實現細節與 Windows API 互動

_wassert 位於 assert.c,主要邏輯:

  1. 透過 _set_error_mode 判斷錯誤輸出模式:_OUT_TO_STDERR (標準錯誤) 或 _OUT_TO_DEFAULT (視應用程式類型而定)。
  2. 主控台應用程式:取得 STD_ERROR_HANDLE,使用 swprintf 格式化訊息,再呼叫 WriteConsoleW 輸出至主控台,最後呼叫 abort() 終止程式。
  3. GUI 應用程式:構建訊息字串,呼叫內部函式 __crtMessageBox 彈出訊息方塊 (MessageBoxW)。

__crtMessageBox 動態載入 User32.dll 的設計考量

原始碼顯示 __crtMessageBox 使用 LoadLibrary + GetProcAddress 動態取得 MessageBoxW 等 API 位址,而非直接連結。這是為了:

  • 避免啟動時強制載入 User32.dll (節省資源)。
  • 相容不同 Windows 版本 User32.dll 可能的載入位址變化 (雖然 Kernel32/User32 通常固定基址,但非契約保證)。
  • 支援指標編碼 (_encode_pointer/_decode_pointer) 防禦惡意覆寫。

經典教訓:assert 宏不應直接呼叫庫函式

K&R 《C 程式設計語言》 指出:assert 宏不應直接呼叫 fprintf 等庫函式,因為:

  1. 程式可能未包含 <stdio.h>,導致 stderr、fprintf 未宣告。
  2. 使用者可能以宏重新定義這些名稱。
  3. 宏必須展開為 void 表達式,以支援 (assert(expr), other_expr) 這類逗號運算式語境。
然而 Microsoft CRT 實現中 _wassert 內部直接使用 swprintf、_ftprintf 等函式,屬於「知道自己在做什麼」的實用主義妥協。

遞迴風險與 strcpy 面試題反思

面試經典題「實作 strcpy」常見答案使用 assert(strDest && strSrc)。但若 assert 內部實現 (如格式化輸出) 間接呼叫了 strcpy,將導致遞迴無限循環。這提醒我們:標準庫實現必須極度謹慎避免循環依賴,也是為何標準要求 assert 呼叫「秘密名稱」函式的原因。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved