assert宏的原型定義在頭文件assert.h中,它的作用是如果宏後面的條件返回假,則終止程序的執行,該宏會調用__assert_fail函數,這個函數內部會先向stderr輸出錯誤信息,然後調用abort函數來終止程序的執行。
一,assert宏的定義
如下:
[cpp]
# define assert(expr) \
((expr) \
? __ASSERT_VOID_CAST (0) \
: __assert_fail (__STRING(expr), __FILE__, __LINE__, __ASSERT_FUNCTION))
__assert_fail函數是一個庫導出函數,導出定義如下:
[cpp]
/* This prints an "Assertion failed" message and aborts. */
extern void __assert_fail (__const char *__assertion, __const char *__file,
unsigned int __line, __const char *__function)
__THROW __attribute__ ((__noreturn__));
注意上面的__attribute__((__noreturn__))定義,這個屬性通知編譯器,該函數從不返回。運行完這個函數程序就退出來了。
二,禁用assert宏
這裡要注意一個宏,這個宏很有用,可以禁用assert宏,是如何做到的呢?
[cpp]
#ifdef NDEBUG
# define assert(expr) (__ASSERT_VOID_CAST (0))
/* void assert_perror (int errnum);
If NDEBUG is defined, do nothing. If not, and ERRNUM is not zero, print an
error message with the error text for ERRNUM and abort.
(This is a GNU extension.) */
# ifdef __USE_GNU
# define assert_perror(errnum) (__ASSERT_VOID_CAST (0))
# endif
#else /* Not NDEBUG. */
所以,就是如果我們自己定義了NDEBUG宏的話,assert就不會工作了,因為__ASSERT_VOID_CAST宏定義如下:
[cpp]
#if defined __cplusplus && __GNUC_PREREQ (2,95)
# define __ASSERT_VOID_CAST static_cast<void>
#else
# define __ASSERT_VOID_CAST (void)
#endif
三,assert宏注意事項
最好使用assert宏檢查一個條件,就是不要用&&或者||操作符,這樣更容易發現是哪個條件出現問題,在需要的時候,多寫幾個assert宏。
不要使用assert進行變量修改,如assert(k++>10),因為我們可能會禁用這個宏,此時,k++是不會執行的,正如上面我們看到的一樣。
assert不是條件過濾。
四,自定義實現ASSERT宏
我們自己可以定義實現ASSERT宏,這樣可以得到更多的錯誤信息,也可以自己定義錯誤行為。
[cpp]
#ifndef ASSERT
#define ASSERT(x) \
(void)Assert((x), __FUNCTION__, __FILE__, __LINE__, #x)
#endif
Assert實現如下:
[cpp]
inline bool Assert(bool result, const char* function, const char* file,
int line, const char* expression) {
if (!result) {
Log_Assert(function, file, line, expression);
Break();
return false;
}
return true;
}
上面的Log_Assert函數只用於輸出錯誤信息。
Break定義對錯誤處理的行為,該函數定義如下:
[cpp]
void Break() {
#if WIN32
::DebugBreak();
#elif OSX // !WIN32
::Debugger();
#else // !OSX && !WIN32
#if _DEBUG_HAVE_BACKTRACE
OutputTrace();
#endif
abort();
#endif // !OSX && !WIN32
}
DebugBreak是一個VC的庫函數,可以對進程附加調試信息,還可以加斷點什麼的,其實就是我們有時會遇到的問我們是否要調試,如果我們選擇是的話,就會啟動一個開發環境,打開調試器。
最好的話,實現ASSERT時,啟用命名空間,這樣就更容易控制這個宏了。