宏定义的黑魔法——assert原理详解
Posted C_YCBX Py_YYDS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了宏定义的黑魔法——assert原理详解相关的知识,希望对你有一定的参考价值。
文章目录
assert在程序中的用法
assert 意为断言,顾名思义,可以将执行语句直接拦腰折断,我们先聊聊基本用法,再聊一聊需要注意在哪个时候使用。最后再深度剖析源代码实现。
怎么用?
基本用法:对前面的未知参数进行假设限定
#include<assert.h>
#include<stdlib.h>
#include<stdio.h>
int creat(int len)
assert(len>0); //一旦len不大于0则会导致程序中断
int* ret = NULL;
if((ret = malloc(sizeof(int)*len)==NULL)
perror("malloc");
exit(-1);
return ret;
应该注意什么?
注意:assert的断言只能在debug时可用,一旦程序进入release发布状态,则assert将不会对程序进行阻断!assert实际上是一个很简单的宏定义,这个后面会解析。
为什么用?
由于assert用起来非常的简单,而且很直观,如果你有用过抛异常等等其他的处理方式,你会对assert感到非常舒服,因为它写起来相比其他的异常处理方式用法要简单太多太多,但很快也能发现它的局限性,assert在程序release后就无法发挥作用了,也就是说assert并不能增加你发布程序的健壮性!但是它能在调试的时候发挥巨大作用,能够很快的定位错误,进行更改,而且使用assert后代码可读性也大大增加。
所以我个人认为,使用assert最大的原因在于可以让你写代码的逻辑连贯,而暂时不需要去考虑如何处理外界引发的错误,到了程序将要发布的阶段再根据断言对源代码进行一定的修改即可,如果你每次写一段代码就catch来考虑异常处理这样很难集中精神去干一件事。
什么时候使用?
根据为什么用,我们清楚了,assert适合我们在编写程序的时候进行逻辑断言,具体而言,是对外界未知条件的确定,从而使得程序逻辑严密。所以一般放在函数的开始部分用来对外界传入的参数进行限制,而如果是调用其他函数产生的错误,我们应该进行异常处理,而不是断言。
assert()的源代码解析
assert.h 文件完整代码:
以上代码仅仅作为展示,实际上这段代码中真正有用的部分就以下这段代码而已:
一、当定义了NDEGUB宏
首先我们看到预处理命令 #ifdef NDEGUG
这个 NDEBUG 宏,会在程序编译为release版本时编译器会自动定义 NDEBUG
这个宏, 而debug版本不会定义,所以debug时只会执行 #else
里的语句,所以根据以上代码可知,一旦程序为release版本,则assert宏只会被展开为 ((void)0)
。示例如下:
void func(int len)
assert(len>0); =>等价于 ((void)0);
//而至于为什么要强转为void,因为void型,是不能用来赋值给其他任意类型
//如:以下是会报错的
int a = (void)0;
强转void避免了使用assert宏的返回值,实际上也不能使用它的返回值。
二、当未定义NDEBUG宏
而如果是debug版本则以上所用的 assert(len>0);
将转为以下代码:
基本的宏定义知识:在某个宏前面+’#’,则被替换时会在左右加上"",作为字符串。
void func()
assert(len>0); => (void)((!!(len>0)) ||(_wassert(_CRT_WIDE(#len>0), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) );
//简化整理得:(把与理解无关的宏定义去掉)
void func()
(void)((!!(len>0))||(_wassert(#len,__FILE__,__LINE__),0));
我们最终把assert(expr);
这个宏简化为了:
(void)( ( !!(expr) ) || ( _wassert( #expr,FILE,LINE ) , 0 ) );
我们以 ||
为分隔,分为 Part1 和 Part2 来讲解
注意:这里的 expr 指代的是 assert(expr) 里面传进来的参数。
Part1:!!(expr)
,即对表达式求两次反,如果expr
为真,则!!(expr)
为1;如果expr
为假,则expr
为0。两次求反,可以防止用户恶意传的值(非0、非1的值),!!(expr)
要么为1,要么为0,不能为其他值了。
Part2:(_wassert(表达式字符串, 文件名, 行号), 0)
,#expr中#是一个构串符,将宏参数转成一个字符串,__FILE__和__LINE__分别对应包含assert宏的文件名和assert宏在该文件中的行号。这里_wassert的声明如下:
_ACRTIMP void __cdecl _wassert(
_In_z_ wchar_t const* _Message,
_In_z_ wchar_t const* _File,
_In_ unsigned _Line
);
这函数的定义由运行时环境提供,没有源代码,知道下面几点就可以了。
(1)_wassert
函数在part1为假,才会被调用;part1为真时,不会被调用。根据 ||
操作符的短路特性。
(2)_wassert
函数和后面的0,构成一个逗号表达式,因此part2的返回值是0。因此 ( (part1) || (part2) )
的返回值为0或1取决于part1。
三、assert执行过程总结
经过Part1和Part2的分析,我们清楚了assert宏的实现原理:
- 在debug下传入宏参数,展开为一个或表达式,而release下传入,则只会产生
((void)0)
。 - 根据或表达式的判断结果,如果第一个判断结果为1,则或短路,不会再执行后面的语句了。
- 如果第一个判断结果为0,则继续执行下一段语句,下一段语句调用一个函数来中断程序的执行,并打印出相应的信息。
根据原理实现简单的assert()宏定义
一、宏定义实现
#ifdef NDEBUG //当程序为发布状态时直接定义为下面这个空语句宏
#define assert(expression) ((void)0)
#else
#define assert(expression) (void)( \\
(!!(expression))|| \\
(_assert(#expression,__FILE__,(unsigned)(__LINE__)),0)\\
)
#endif
二、打印输出函数实现
除了打印以外,我们还需要把程序中断(退出),用exit退出即可。
void __cdecl _assert(//错误信息打印
const char *_Message,
const char * _File,
unsigned _Line)
printf("Assertion failed!\\n");
fputc('\\n',stdout);
printf("File: %s, Line %d\\n",_File,_Line);
fputc('\\n',stdout);
printf("Expression: %s",_Message);
exit(3);
三、最终效果
实现源码:
测试结果:
测试代码:
#include "assert.h"
int main()
int a = 32;
int b = 322;
assert(a==b);
while (1);//一旦a==b为假,则会一直无限循环
执行结果:
以上是关于宏定义的黑魔法——assert原理详解的主要内容,如果未能解决你的问题,请参考以下文章