assert() 断言函数,用于在调试过程中捕捉程序错误

本文详细介绍了断言assert的使用方法和机制,包括如何检测程序错误,处理表达式,以及在调试和发布模式下的应用。同时,文章强调了使用assert时的注意事项,避免复杂表达式和环境改变语句。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载:http://c.biancheng.net/ref/assert.html

void assert (int expression);

断言函数,用于在调试过程中捕捉程序的错误。

“断言”在语文中的意思是“断定”、“十分肯定地说”,在编程中是指对某种假设条件进行检测,如果条件成立就不进行任何操作,如果条件不成立就捕捉到这种错误,并打印出错误信息,终止程序执行。

assert() 会对表达式expression进行检测:

1、如果expression的结果为 0(条件不成立),那么断言失败,表明程序出错,assert() 会向标准输出设备(一般是显示器)打印一条错误信息,并调用 abort() 函数终止程序的执行。
2、如果expression的结果为非 0(条件成立),那么断言成功,表明程序正确,assert() 不进行任何操作。

要打印的错误信息的格式和内容没有统一的规定,这和标准库的具体实现有关(也可以说和编译器有关),但是错误信息至少应该包含以下几个方面的信息:

1、断言失败的表达式,也即expression;
2、源文件名称;
3、断言失败的代码的行号;

大部分编译器的格式如下所示:

Assertion failed: expression, file filename, line number

参数:

expression

要检测的表达式。如果表达式的值为 0,那么断言失败,程序终止执行;如果表达式的值为非 0,那么断言成功,assert() 不进行任何操作。

返回值:无返回值

assert() 的用法和机制

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

assert() 的用法很简单,我们只要传入一个表达式,它会计算这个表达式的结果:如果表达式的结果为“假”,assert() 会打印出断言失败的信息,并调用 abort() 函数终止程序的执行;如果表达式的结果为“真”,assert() 就什么也不做,程序继续往后执行。

下面是一个具体的例子:

#include <stdio.h>
#include <assert.h>
int main(){
    int m, n, result;
    scanf("%d %d", &m, &n);
    assert(n != 0);  //写作 assert(n) 更加简洁
    result = m / n;
    printf("result = %d\n", result);
    return 0;
}

本例用来计算两个数相除的结果,由于被除数不能为 0,所以我们加入了 assert() 来检测错误。

如果输入100 20,那么 n 的值为 20,n != 0这个条件成立,assert() 不进行任何操作,最终的输出结果为:

100 20
result = 5

如果输入100 0,那么 n 的值为 0,n != 0这个条件不成立,assert() 就会报告错误,并终止程序执行,最终的结果如下所示:

100 0
Assertion failed: n != 0,  file E:\\CDemo\main.c, line 6

NDEBUG 宏
如果查看 <assert.h> 头文件的源码,会发现 assert() 被定义为下面的样子:

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

这意味着,一旦定义了NDEBUG宏,assert() 就无效了。

NDEBUG 是”No Debug“的意思,也即“非调试”。有的编译器(例如 Visual Studio)在发布(Release)模式下会定义 NDEBUG 宏,在调试(Debug)模式下不会定义定义这个宏;有的编译器(例如 Xcode)在发布模式和调试模式下都不会定义 NDEBUG 宏,这样当我们以发布模式编译程序时,就必须自己在编译参数中增加 NDEBUG 宏,或者在包含 <assert.h> 头文件之前定义 NDEBUG 宏。

调试模式是程序员在测试代码期间使用的编译模式,发布模式是将程序提供给用户时使用的编译模式。在发布模式下,我们不应该再依赖 assert() 宏,因为程序一旦出错,assert() 会抛出一段用户看不懂的提示信息,并毫无预警地终止程序执行,这样会严重影响软件的用户体验,所以在发布模式下应该让 assert() 失效。

修改上面的代码,在包含 <assert.h> 之前定义 NDEBUG 宏:

#define NDEBUG

#include <stdio.h>
#include <assert.h>

int main(){
    int m, n, result;
    scanf("%d %d", &m, &n);
    assert(n);
    result = m / n;
    printf("result = %d\n", result);
    return 0;
}

当以发布模式编译这段代码时,assert() 就会失效。如果希望继续以调试模式编译这段代码,去掉 NDEBUG 宏即可。

在代码中显式地增加 NDEBUG 宏比较麻烦,因为当以调试模式编译代码时还得再去掉它,更加科学的做法是在 IDE 的编译参数中添加 NDEBUG 宏。不同的 IDE 添加宏的方式不同,这里不再深入探讨。

注意事项

(1) 使用 assert() 时,被检测的表达式最好不要太复杂,以下面的代码为例:

assert( expression1 && expression2 && expression3);

当发生错误时,assert() 只会告诉我们expression1 && expression2 && expression3整个表达式为不成立,但是这个大的表达式还包含了三个小的表达式,并且它们之间是&&运算,任何一个小表达式为不成立都会导致整个表达式为不成立,这样我们就无法推断到底是expression1有问题,还是expression2或者expression3有问题,从而给排错带来麻烦。

这里我们应该遵循使用 assert() 的一个原则:每次断言只能检验一个表达式。根据这个原则,上面的代码应改为:

assert(expression1);
assert(expression2);
assert(expression3);

如此,一旦程序出错,我们就知道是哪个小的表达式断言失败了,从而快速定位到有问题的代码。

(2) 使用 assert() 的另外一个注意事项是:不要用会改变环境的语句作为断言的表达式。请看下面的代码:

#include <stdio.h>
#include <assert.h>

int main(){
    int i = 0;
    while(i <= 110){
        assert(++i <= 100);
        printf("我是第%d行\n",i);
    }
    return 0;
}

在 Debug 模式下运行,程序循环到第 101 次时,i 的值为 100,++i <= 100不再成立,断言失败,程序终止运行。

在 Release 模式下运行,编译参数中设置了 NDEBUG 宏(如果编译器没有默认设置,那么需要你自己来设置),assert() 会失效,++i <= 100这个表达式也不起作用了,while() 无法终止,成为一个死循环。

定义了 NDEBUG 宏后,assert(++i <= 100)会被替换为((void)0)

导致程序在 Debug 模式和 Release 模式下的运行结果大相径庭的罪魁祸首就是++运算,我们本来希望它改变 i 的值,逐渐达到 while 的终止条件,但是在 Release 模式下它失效了。

修改这段不规范的代码也很容易,将++i移出 assert() 即可:

#include <stdio.h>
#include <assert.h>

int main(){
    int i = 0;
    while(i <= 110){
        ++i;
        assert(i <= 100);
        printf("我是第%d行\n",i);
    }
    return 0;
}

如此,不管是 Debug 模式还是 Release 模式,每次循环 i 的值都会增加,逐渐达到 while 的终止条件时,结束循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值