乐趣区

关于c:防御式编程之断言assert的使用

  进攻式编程的重点就是须要进攻一些程序未曾意料的谬误,这是一种进步软件品质的辅助性办法,断言 assert 就用于进攻式编程,编写代码时,咱们总是会做出一些假如,断言就是用于在代码中捕获这些假如。应用断言是为了验证预期的后果——当程序执行到断言的地位时,对应的断言应该为真;若断言不为真时,程序会终止执行,并给出错误信息。能够在任何时候启用和禁用断言验证,因而能够在程序调试时启用断言而在程序公布时禁用断言。同样,程序投入运行后,最终用户在遇到问题时能够从新启用断言。

1、原型函数

  在大部分编译器下,assert() 是一个宏;在多数的编译器下,assert() 就是一个函数。咱们不须要关怀这些差别,能够只把 assert() 当作函数应用即可。即:

void assert(int expression);

  在程序运行时它会计算括号内的表达式,如果 expression 为非 0 阐明其值为真,assert() 不执行任何动作,程序继续执行前面的语句;如果 expression 为 0 阐明其值为假,assert() 将会报告谬误,并终止程序的执行,值得理解的是,程序终止是调用 abort() 函数,这个函数性能就是终止程序执行,间接从调用的中央跳出,abort() 函数也是规范库函数,在 <stdlib.h> 中定义。 因而 assert() 用来判断程序中是否呈现了显著非法的逻辑,如果呈现了就终止程序免得导致严重后果,同时也便于查找谬误。

2、具体释义

  assert() 在 c 规范库中的 <assert.h> 中被定义。上面就看下在 assert.h 中的定义:

#ifdef NDEBUG
#define assert(e) ((void)0)
#else
#define assert(e)  ((void) ((e) ? ((void)0) : __assert (#e, __FILE__, __LINE__)))
#endif

   能够看到在定义了 NDEBUG 时,assert() 有效,只有在未定义 NDEBUG 时,assert() 才实现具体的函数性能。NDEBUG 是“No Debug”的意思,也即“非调试”。程序个别分为 Debug 版本和 Release 版本,Debug 版本是程序员在测试代码期间应用的编译版本,Release 版本是将程序提供给用户时应用的公布版本,一般来说断言 assert() 是仅在 Debug 版本起作用的宏。在公布版本时,咱们不应该再依赖 assert() 宏,因为程序一旦出错,assert() 会抛出一段用户看不懂的提示信息,并毫无预警地终止程序执行,这样会重大影响软件的用户体验,所以在公布模式下应该让 assert() 生效,另外在程序中频繁的调用 assert() 会影响程序的性能,减少额定的开销。因而能够在 <assert.h> 中定义 NDEBUG 宏,将 assert() 性能敞开。

#define NDEBUG  // 定义 NDEBUG  
#ifdef NDEBUG
#define assert(e) ((void)0)
#else
#define assert(e) ((void) ((e) ? ((void)0) : __assert (#e, __FILE__, __LINE__)))
#endif

定义 NDBUG 时:

  当定义了 NDEBUG 之后,assert() 执行的具体函数就变成了 ((void)0),这示意啥也不干了,宏外面这样用的目标是避免该宏被用作右值,因为 void 类型不能用作右值。所以当在头文件中定义了 NDEBUG 之后,assert() 的检测性能就主动生效了。

未定义 NDBUG 时:

  能够看到 assert() 执行实际上是通过三目运算符来判断表达式 e 的虚实,执行相应的解决。当表达式 e 为真时,执行 (void)0,即什么也不执行,程序持续运行;当表达式 e 为假时,那么它会打印进去 assert 的内容、以后的文件名、以后行号,接着终止程序执行。

3、用法举例

  在未定义 NDBUG 时,assert() 性能失效的状况下,来看一个简略的 assert() 应用的例子:

#include <stdio.h>
#include <assert.h>
void main()
{
    int i = 8;
    assert(i > 0);
    printf("i = %d\n", i);
    i = -8;
    assert(i > 0);
    printf("i = %d\n", i);
}

  能够看出在程序中应用 assert(i > 0) 来判断;当 i > 0 时,assert 的判断表达式为真,assert 不失效;当 i < 0 时,assert 的判断表达式为假,assert 失效。

  在程序第 5 行 i = 8,执行完 assert 后,程序将执行后续的 printf 打印出 i 的值;而在第 8 行 i = -8,执行完 assert 后,程序将终止,不会执行后续的 printf。

4、应用注意事项

   应用 assert 的外围准则是:用于解决绝不应该产生的状况, 这就是为什么应该在程序 Debug 版本中应用,这是为了将主观上不应该产生的谬误在程序 Debug 版本中就应该解决掉,从而在程序 Release 版本时不会产生这种不应该产生的类型的谬误。

和 if 的区别

  assert 用函数来判断是否满足表达式条件后终止程序,在 Debug 版本中用 assert 来判断程序的合法性,定位不容许产生的谬误,那么什么是不应该产生的谬误,例如像上面这种除 0 操作,主观上就不应该产生,就是就要在 Debug 版本中查看排除掉这种谬误,免得影响后续程序的执行。

#include <stdio.h>
#include <assert.h>
void fun(int a, int b)
{assert(b != 0);
    int i = a / b;
}

  if 是一个关键字,个别用于依据条件来判断逻辑的正确性,即是否依据条件对应执行,Debug 和 Release 版本中都能够应用,例如上面用 if 的时候,就容许这些判断条件是失常产生的,是正当的,须要依据产生的条件执行对应的逻辑,程序能够往下执行。

#include <stdio.h>
#include <assert.h>
void fun(int a, int b)
{if(a > 0)
       ...
   else if(a < 0)
       ...
   else
       ...
}

  因而在应用前,能够先判断下,如果逻辑不容许产生,那么就应用 assert 在 Debug 阶段将问题解决掉;如果逻辑容许的,那么就应用 if,当然也能够用 if 判断后进行条件的 return 操作,来杜绝不容许逻辑,实质是避免谬误的逻辑影响后续程序的执行。例如上述的用来判断除 0 操作的例子也能够用 if:

#include <stdio.h>
#include <assert.h>
void fun(int a, int b)
{if(0 == b)
        return;
    int i = a / b;
}

用于判断函数的入参

  个别 assert 能够用于判断函数入参的合法性,比方入参值是否合乎,指针是否为空:

#include <stdio.h>
#include <assert.h>
void fun1(int a)
{assert(a > 0);
    ...
}
void fun2(int *p)
{assert(p != NULL);
    ...
}

不要应用影响失常逻辑的判断条件语句

  assert 的判断条件语句肯定是确定的,在 Debug 版本中应用的排除掉谬误的条件逻辑,不要影响到 Release 版本时的失常逻辑。例如上面的例子,在 Debug 版本时,i++ 到 >=100 时,assert 失效,程序终止;然而到了 Release 版本,因为要减少 NDEBUG 宏,assert() 有效。assert(i++ < 100) 就变成了空操作 (void)0;因为没有 i ++ 语句执行,那么 while 成了死循环。

#include <stdio.h>
#include <assert.h>
void main()
{
    int i = 0;
    while(i <= 110)
    {assert(i++ < 100);
        printf("i = %d\n",i);
    }
}

不要用多个判断条件语句

  个别一个 assert 只用一个判断语句来实现,如果在一个 assert 中应用多条判断语句,当谬误产生时,会不晓得是哪个条件语句呈现谬误,谬误体现的就不直观。

#include <stdio.h>
#include <assert.h>
void fun1(int a, int b) // 谬误应用
{assert(a > 0 && b > 5);
    ...
}
void fun2(int a, int b) // 正确应用
{assert(a > 0);
    assert(b > 5);
    ...
}

更多技术内容和书籍材料获取敬请关注公众号“明解嵌入式”

退出移动版