一、看一看断言实现:
在Linux 中的/usr/include/assert.h中,我们可以看看assert宏的定义:
#ifdef NDEBUG # define assert(expr) (__ASSERT_VOID_CAST (0)) ... #endif
#if defined __cplusplus && __GNUC_PREREQ (2,95) # define __ASSERT_VOID_CAST static_cast<void> // 如果为c++ #else # define __ASSERT_VOID_CAST (void) // 如果为c #endif
当定义NDEBUG时,那么 assert(expr)实际为:static_cast<void>(0)或(void)(0) 可以看出,不论是c还是c++,assert在NDEBUG时,Do nothing!
那如果要禁用assert,很简单,在程序中 #define NDEBUG ,assert就Do noting了接着向下看:(为了简单起见,只讨论c语言)
# define assert(expr) \ ((expr) \ ? __ASSERT_VOID_CAST (0) \ : __assert_fail (__STRING(expr), __FILE__, __LINE__, __ASSERT_FUNCTION))
当expr为真时,它执行__ASSERT_VOID_CAST(0),也是相当于什么也不做嘛!
如果expr为假,则执行: __assert_fail (__STRING(expr), __FILE__, __LINE__, __ASSERT_FUNCTION)) 我们层层展开来: /* 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__));其中:__STRING()会把expr转换成字符串;__FILE__为.c或.cpp文件文件名称;__LINE__为代码所在的行数;__ASSERT_FUNCTION为一个特殊的变量,简单的说它含有定义它的函数名称(具体解释在头文件注释中有详细说明)。
你也可以写一个简单程序看一下这几个变量:
运行结果如下:
这几个特殊的变量传给__assert_fail函数,此函数为一个库导出函数,注释中描述这个函数作用是向错误输出中打印"Assertion failed"信息,然后调用abort函数来终止程序执行。
assert宏的原型定义在头文件assert.h中,它的作用是如果宏后面的条件返回假,则终止程序的执行,该宏会调用__assert_fail函数,这个函数内部会先向stderr输出错误信息,然后调用abort函数来终止程序的执行。其中__attribute__((__noreturn__))属性通知编译器,该函数从不返回。
二,assert宏注意事项
1.最好使用assert宏检查一个条件,就是不要用&&或者||操作符,这样更容易发现是哪个条件出现问题,在需要的时候,多写几个assert宏。 2.不要使用assert进行变量修改,如assert(k++>10),因为我们可能会禁用这个宏,此时,k++是不会执行的,正如上面我们看到的一样。 3.assert不是条件过滤。
三,自定义实现ASSERT宏
我们自己可以定义实现ASSERT宏,这样可以得到更多的错误信息,也可以自己定义错误行为。 #ifndef MYASSERT #define MYASSERT(x) \ (void)Assert((x), __FUNCTION__, __FILE__, __LINE__, #x) #endif Assert实现如下: inline bool Assert(bool result, const char* function, const char* file, int line, const char* expression) { if (!result) { assertFunc(function, file, line, expression);//你可以打印到错误输出或者写入log
Break(); return false; } return true; } Break定义对错误处理的行为,该函数定义如下:
void Break() { #if WIN32 ::DebugBreak(); #elif OSX // !WIN32 ::Debugger(); #else // !OSX && !WIN32 #if _DEBUG_HAVE_BACKTRACE OutputTrace(); #endif abort(); #endif // !OSX && !WIN32 } DebugBreak是一个VC的库函数,可以对进程附加调试信息,还可以加断点什么的,其实就是我们有时会遇到的问我们是否要调试,如果我们选择是的话,就会启动一个开发环境,打开调试器。 最好的话,实现MYASSERT时,启用命名空间,这样就更容易控制这个宏了。