3

防御式编程之断言assert的使用 - Sharemaker

 1 year ago
source link: https://www.cnblogs.com/Sharemaker/p/16954224.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

防御式编程的重点就是需要防御一些程序未曾预料的错误,这是一种提高软件质量的辅助性方法,断言assert就用于防御式编程,编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设。使用断言是为了验证预期的结果——当程序执行到断言的位置时,对应的断言应该为真;若断言不为真时,程序会终止执行,并给出错误信息。可以在任何时候启用和禁用断言验证,因此可以在程序调试时启用断言而在程序发布时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言。

1、原型函数

  在大部分编译器下,assert() 是一个宏;在少数的编译器下,assert() 就是一个函数。我们不需要关心这些差异,可以只把 assert()当作函数使用即可。即:

1 void assert(int expression);

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

2、详细释义

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

1 #ifdef NDEBUG
2 #define assert(e) ((void)0)
3 #else
4 #define assert(e)  ((void) ((e) ? ((void)0) : __assert (#e, __FILE__, __LINE__)))
5 #endif

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

1 #define NDEBUG  //定义NDEBUG  
2 #ifdef NDEBUG
3 #define assert(e) ((void)0)
4 #else
5 #define assert(e) ((void) ((e) ? ((void)0) : __assert (#e, __FILE__, __LINE__)))
6 #endif
  • 定义NDEBUG时:

  当定义了NDEBUG之后,assert()执行的具体函数就变成了 ((void)0),这表示啥也不干了,宏里面这样用的目的是防止该宏被用作右值,因为void类型不能用作右值。所以当在头文件中定义了NDEBUG之后,assert()的检测功能就自动失效了。

  • 未定义NDEBUG时:

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

3、用法举例

  在未定义NDEBUG时,assert()功能生效的情况下,来看一个简单的assert()使用的例子:

 1 #include <stdio.h>
 2 #include <assert.h>
 3 void main()
 4 {
 5     int i = 8;
 6     assert(i > 0);
 7     printf("i = %d\n", i);
 8     i = -8;
 9     assert(i > 0);
10     printf("i = %d\n", i);
11 }

  可以看出在程序中使用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版本中检查排除掉这种错误,以免影响后续程序的执行。

1 #include <stdio.h>
2 #include <assert.h>
3 void fun(int a, int b)
4 {
5     assert(b != 0);
6     int i = a / b;
7 }

  if是一个关键字,一般用于根据条件来判断逻辑的正确性,即是否根据条件对应执行,Debug和Release版本中都可以使用,例如下面用if的时候,就允许这些判断条件是正常发生的,是合理的,需要根据发生的条件执行对应的逻辑,程序可以往下执行。

 1 #include <stdio.h>
 2 #include <assert.h>
 3 void fun(int a, int b)
 4 {
 5    if(a > 0)
 6        ...
 7    else if(a < 0)
 8        ...
 9    else
10        ...
11 }

  因此在使用前,可以先判断下,如果逻辑不允许发生,那么就使用assert在Debug阶段将问题解决掉;如果逻辑允许的,那么就使用if,当然也可以用if判断后进行条件的return操作,来杜绝不允许逻辑,本质是防止错误的逻辑影响后续程序的执行。例如上述的用来判断除0操作的例子也可以用if:

1 #include <stdio.h>
2 #include <assert.h>
3 void fun(int a, int b)
4 {
5     if(0 == b)
6         return;
7     int i = a / b;
8 }
  • 用于判断函数的入参

  一般assert可以用于判断函数入参的合法性,比如入参值是否符合,指针是否为空:

 1 #include <stdio.h>
 2 #include <assert.h>
 3 void fun1(int a)
 4 {
 5     assert(a > 0);
 6     ...
 7 }
 8 void fun2(int *p)
 9 {
10     assert(p != NULL);
11     ...
12 }
  • 不要使用影响正常逻辑的判断条件语句

  assert的判断条件语句一定是确定的,在Debug版本中使用的排除掉错误的条件逻辑,不要影响到Release版本时的正常逻辑。例如下面的例子,在Debug版本时,i++到>=100时,assert生效,程序终止;但是到了Release版本,由于要增加NDEBUG宏,assert()无效。assert(i++ < 100)就变成了空操作(void)0;由于没有i++语句执行,那么while成了死循环。

 1 #include <stdio.h>
 2 #include <assert.h>
 3 void main()
 4 {
 5     int i = 0;
 6     while(i <= 110)
 7     {
 8         assert(i++ < 100);
 9         printf("i = %d\n",i);
10     }
11 }
  • 不要用多个判断条件语句

  一般一个assert只用一个判断语句来实现,如果在一个assert中使用多条判断语句,当错误发生时,会不知道是哪个条件语句出现错误,错误表现的就不直观。

 1 #include <stdio.h>
 2 #include <assert.h>
 3 void fun1(int a, int b) //错误使用
 4 {
 5     assert(a > 0 && b > 5);
 6     ...
 7 }
 8 void fun2(int a, int b) //正确使用
 9 {
10     assert(a > 0);
11     assert(b > 5);
12     ...
13 }

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

2999540-20221022000235287-740833429.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK