assert是一个宏,用于在调试(debug)期间捕捉不应该发生的错误。它的具体用法详见“捕捉错误的assert”一文。
assert宏在<assert.h>文件中的代码是这样的:
#ifdef NDEBUG
#define assert(_Expression) ((void)0)
#else /* !defined (NDEBUG) */
#if defined(_UNICODE) || defined(UNICODE)
#define assert(_Expression) \
(void) \
((!!(_Expression)) || \
(_wassert(_CRT_WIDE(#_Expression),_CRT_WIDE(__FILE__),__LINE__),0))
#else /* not unicode */
#define assert(_Expression) \
(void) \
((!!(_Expression)) || \
(_assert(#_Expression,__FILE__,__LINE__),0))
#endif /* _UNICODE||UNICODE */
#endif /* !defined (NDEBUG) */
这段代码看起来很唬人,和平时看到的C语言代码长得不太一样(有点像日本人名,什么井上、井旁),老金也很费了一番功夫研究。
代码说明:
1.#ifdef…#else…#endif:条件编译,和if-else语句差不多。ifdef是if define的缩写,检查后面的宏是否定义。如果定义了就执行#ifdef后面的语句,如果没定义,就执行#else后面的语句。上面的代码#ifdef NDEBUG即用于检查是否定义了NDEBUG宏。NDEBUG通常用于区分程序的调试版本和发布版本。在发布版本中,开发者通常希望禁用assert宏,因为它可能会在运行时产生额外的开销(比如性能损失或日志输出)。
2.#define assert(_Expression) ((void)0):这是一个宏替换,它定义了一个名为 assert 的宏,它接受一个参数_Expression。_Expression只是一个普通的标识符,类似于普通函数中的参数。这句代码的意思是说,如果NDEBUG被定义了,assert宏将被替换为一个什么也不做的表达式((void)0)。(void)0是一个将整数0转换为void类型的表达式。0是个整数,表示没有数量,但它毕竟还是个数,还能给变量赋值,但一旦转换为void型,这个功能也丧失了,所以(void)0相当于什么也不干。这样,任何使用assert 的地方在编译时都会被替换为((void)0),从而在运行时不会执行任何操作。也就是说,通过这样的方式,使代码中的assert不起作用。这种手段咱们可以学习下。
3.未定义NDEBUG时的宏替换。在未定义NDEBUG时(即#else /* !defined (NDEBUG) */和#endif /* !defined (NDEBUG) */之间的代码),又是一个#ifdef…#else…#endif,根据是否定义了_UNICODE或UNICODE宏分别给出对assert不同的宏替换。
①Unicode版本的宏替换:如定义了_UNICODE或UNICODE宏,表示要在宽字符(Unicode)环境中打印错误消息。
#define assert(_Expression) \
(void) \
((!!(_Expression)) || \
(_wassert(_CRT_WIDE(#_Expression),_CRT_WIDE(__FILE__),__LINE__),0))
这里,
a) assert(_Expression) 是要替换的宏,_Expression是要检查的条件。
b) \ 是宏定义中的续行符。
c) (void) 表示强制类型转换。在执行宏代码时,可能会产生某个值,这个值可能会被用于程序中其他代码的计算或导致其他不期望的副作用。(void)就像打造太监的割鹿刀,能废掉这个值的赋值功能,从而确保宏的在使用时不产生副作用。
d) ((!!(_Expression)) 是一个去杂提纯过程。这里使用了!!操作符来确保_Expression的结果是一个布尔值(0 或 1)。!! 的工作原理是通过第一次取反将_Expression转换为相反的布尔值(如果它是非零值,则结果为 0;如果它是零值,则结果为 1),然后再次取反,从而得到原始的布尔值。然后,这个布尔值被用于 ||(逻辑或)操作符。
e) _wassert(_CRT_WIDE(#_Expression),_CRT_WIDE(__FILE__),__LINE__)
如果_Expression为假(即 0),则||左边的表达式为假,因此会评估右边的表达式。这里调用了_ wassert函数来输出宽字符错误消息,并传递了三个参数:
* `#_Expression`:这是一个预处理器操作符,用于将`_Expression` 转换为字符串字面量(官方称谓,其实就是指一串字符)。这允许`_assert` 函数知道哪个表达式失败了。
* `__FILE__`:这是一个预定义的宏,表示当前源文件的文件名(作为字符串字面量)。
* `__LINE__`:这也是一个预定义的宏,表示当前源代码行号(作为整数)。
参数前的CRT_WIDE是一个编译器自定义的函数,用于将字符串转换为宽字符字符串。
_wassert 函数的具体实现没有给出,但可以推测它会输出一些诊断信息,可能包括上述三个参数的内容,然后可能终止程序。
f) _wassert(_CRT_WIDE(#_Expression),_CRT_WIDE(__FILE__),__LINE__),0
这部分代码中的 ,0 是为了确保整个宏的扩展始终是一个表达式,其值在_Expression为真时为_Expression的值(由于 || 短路),而在 _Expression为假时为 0(因为逗号表达式的值是最后一个表达式的值)。这确保了宏可以安全地用于任何期望一个表达式(即使该表达式没有副作用)的上下文中。
②非Unicode版本的宏替换:与Unicode版本类似,但这里使用_assert而不是_wassert。因为不需要转换字符串为宽字符。注意_assert是函数,不是宏,因为这里没有#define。
在<assert.h>中,有两个函数的声明:
extern void __cdecl
_wassert(const wchar_t *_Message,const wchar_t *_File,unsigned _Line);
extern void __cdecl
_assert (const char *_Message, const char *_File, unsigned _Line);