首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >C/C++ 学习笔记八(断言与异常处理)

C/C++ 学习笔记八(断言与异常处理)

原创
作者头像
Celebi
修改于 2017-08-28 02:08:48
修改于 2017-08-28 02:08:48
2.6K00
代码可运行
举报
文章被收录于专栏:Celebi的专栏Celebi的专栏
运行总次数:0
代码可运行

断言

断言是什么?简单而言,断言是对某种假设条件进行检查。

C语言中,在assert.h中,断言被定义为宏的形式(assert(expression)),而不是函数。

assert将通过检查表达式的值来决定是否需要终止程序,如果表达式为真(1)则忽略断言,程序继续运行。如果表达式为假(0),那么首先向错误流strerr打印一条错误信息,然后通过abort函数终止程序的运行。

断言用法的简单例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int a,b;
 a = 1;
 b = 1 ;
 assert(b!=0);
 printf("a/b = %d\n",a/b);

通过查看assert.h,NDEBUG宏打开状态时assert宏是可用的。 默认情况下,assert宏只有在Debug版本才起作用,而在Release版本中将被忽略。但在许多操作系统的C程序中,Release版本中也将NDEBUG宏依然为打开状态。

也便是说如果需要用到断言时,用户可以通过重定义自己的ASSERT。例子如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#ifdef DEBUG
#define ASSERT(condition) \
    do{ \
        if(condition) \
        {  \
            NULL; \
        } \
        else{ \
            assert(condition); \
        }     \
    }while(0)
#else
#define ASSERT(condition) NULL
#endif

避免使用断言去检查程序错误

在断言的使用中,应该遵循这样的一个规定:对来自系统内部的可靠数据使用断言,对于外部不可靠数据不能使用断言,而应该使用错误处理代码。

换句话而言,断言是用来处理不应该发生的非法情况,而对于可能发生的应该使用错误处理代码。

对于用户输入,与外部系统进行协议交互时的情况,也不能使用断言进行参数的判断,这种情况属于正常的错误检查。

下面的例子说明了断言的使用场景

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
char * Strdup(const char * src){
    assert(src!=NULL);

    char * result = NULL;
    size_t len = strlen(src) +1;
    result = (char *)malloc(len);

    assert(result != NULL);
    return result;
}

例子中第一个断言assert(src!=NULL)用于判断传入的参数的正确性,保证参数不为NULL 第二个断言assert(result != NULL)检查函数返回值是否为NULL。

例子中的两个断言,第一个是合法的,而第二个不合法,第一个合法是因为传入的参数必须不为NULL,断言如果成功,则说明调用代码存在问题,这属于非法的情况,此处属于断言的正确使用情况。

第二个断言则不同,malloc对于返回NULL的情况属于调用正常情况,这应该使用正常的错误处理逻辑,不应该使用断言。

避免在断言表达式中使用改变上下文的语句

在assert宏只有在Debug版本中情况下,应该避免断言表达式中使用改变环境的语句。

如下例子因为断言语句的缘故,将导致不同的编译版本产生不同的结果。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int test(int i)
{
    assert(i++);
    return i;
}

因此应该避免在断言表达式中使用改变上下文环境的语句,也就是确保断言仅仅作为一个检查而存在,不应该参与正常语句的处理。

异常处理

获取错误代码errno

error 是用于表达不同错误值的一个全局变量。如果一个系统调用或库函数调用失败,可以通过errno的值来确定问题所在。

因errno是一个全局变量,在调用不同系统调用或者库函数失败时都有可能修改它的值,因为在使用errno时,应先将其清0

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    errno = 0;

    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) {

        if (errno!=0) {
            printf("error : %d \n",errno);
            printf("错误信息 : %s \n",strerror(errno));
        }

    }

但errno并不是所有的库函数都适合使用,就error而言库函数一般分为如下几种。

1.函数返回值无法判断错误,需进一步从errno中获取错误信息

函数

返回值

errno值

fgetwc、fputwc

WEOF

EILSEQ

strtol、wcstol

LONG_MIN或LONG_MAX

ERANGE

strtoll、wcstoll

LLONG_MIN或LLONG_MAX

ERANGE

strtoul、wcstoul

ULONG_MAX

ERANGE

strtoull、wcstoull

ULLONG_MAX

ERANGE

strtoumax、wcstoumax

UINTLLONG_MAX

ERANGE

strtod、wcstod

0或者+-HUGE_VAL

ERANGE

strtof、wcstof

0或者+-HUGE_VALF

ERANGE

strtold、wcstold

0或者+-HUGE_VALL

ERANGE

strtoimax、wcstoimax

IMAX_MIN或INTMAX_MAX

ERANGE

以字符串转成长整型函数strtol为例, 在64位机器下,long长度为8字节,最大值LONG_MAX 为 0x7fffffffffffffff,当变量longStr 取超出长整型最大值的字符串”0xffffffffffffffff”和刚好等于最大值的字符串”0x7fffffffffffffff”时,函数的返回值都为相同的LONG_MAX。此时金聪返回值是无法判断函数的执行的成功与否。这个时要判断errno的值。如下例中,会打印出错误的信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    errno =0;

    //LONG_MAX的最大值为0x7fffffffffffffff
    const char * longStr = "0xffffffffffffffff";
    long ret = strtol(longStr,NULL,16);
    if (ret == LONG_MAX) {
        if (errno!=0) {
            printf("error : %d \n",errno);
            printf("错误信息 : %s \n",strerror(errno));
        }else{
            printf("等于long的最大值\n");
        }
    }

2.函数返回值可知错误,errno可知更详细的错误

函数

返回值

errno值

ftell()

-1L

positive

fgetpos()、fsetpos()

nonzero

positive

mbrtowc()、mbsrtowcs()

(size_t)(-1)

EILSEQ

signal()

SIG_ERR

positive

wcrtomb()、wcsrtombs

(size_t)(-1)

EILSEQ

mbrtoc16()、mbrtoc32()

(size_t)(-1)

EILSEQ

c16rtomb()、cr21rtomb

(size_t)(-1)

EILSEQ

3.有不同标准文档的库函数

有些函数在不同的标准下对errno有不同的定义,例如fopen中便是一个例子。C99并没有对使用fopen是对errno做要求,但POSIX.1却声明了错误时返回NULL,并将错误码写入errno。

避免使用goto语句

goto语句有很多优点,例如goto语句可以非常方便的在局部作用域中跳出多层循环,执行如无条件的跳转。 但正因为goto语句可以灵活的跳转,如果不加以限制它会破坏程序的结构化风格,使得代码难以理解与测试,同时不加限制的使用goto语句可能跳过变量的初始化、重要的计算等语句。

以下例子在a小于0或者a小于等于100时会使用goto跳转到标记为Error的语句中。

注意goto只能在局部作用域中跳转。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void testGoto(int a)
{
    if (a>0) {
        if (a>100) {
            printf(" a = %d \n",a);
        }else{
            goto Error;
        }
    }else{
        goto Error;
    }
Error:
    printf("Test Error a = %d \n",a);
}

避免使用setjmp与longjmp

相比与goto语句只能在局部作用域中跳转,setjump与longjmp可以进行跨作用域跳转,也就是跨函数跳转。

我们知道函数调用都以函数栈的形式进行调用与退出,既然要做到跨函数跳转,那便需要对当前的函数栈进行保存与还原,而setjmp的作用便是保存当前函数栈至类型jmp_buf结构体变量中,而longjmp的作用便是从此结构体中恢复,还原函数栈。

而相对于goto仅在作用域内跳转,setjmp和longjmp则使代码更加的难以维护以及可读。

小结

  1. C语言中,使用函数的返回值来标志函数是否执行成功(默认成功返回1,失败返回0)当使用接口时,必须对函数进行正确性的验证,检查它的返回值,并且对每个错误的返回值进行相应的处理以及提示。
  2. 同样的道理,如果作为接口的开发方,需要对函数的各种情况反映到返回值中。
  3. 编写代码是,无论使用什么样的错误处理方式,发现程序中错误最好的方法便是执行程序,让数据在函数中流动,在判断逻辑中查找到函数出错的地方。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C++为什么要引入异常处理机制
在程序设计中,错误时不可避免的。及时有效的发现错误,并作出适当的处理,无论是在软件的开发阶段还是在维护阶段都是至关重要的。错误修复技术是提高代码健壮性的最有效的方法之一。
恋喵大鲤鱼
2018/08/03
1.2K0
C/C++错误信息
在 C/C++ 编程中,错误信息的捕获和处理是保证程序健壮性的重要部分。错误通常通过函数的返回值或者全局变量 errno 来表示。为了方便调试和错误处理,C/C++ 提供了多种函数和方法来获取和输出错误信息。以下是 C/C++ 错误处理的常见方法及函数介绍:
ljw695
2025/03/22
2260
C程序设计的异常处理
大家新年好,感谢大家对本公众号一如既往地支持,后面争取创作出更加优质的文章。今天是2021年的第一篇文章,分享一下在C程序设计当中对异常的处理。主要是介绍一下goto和longjmp函数的使用。
飞哥
2021/01/18
7880
从零开始学C++之异常(一):C语言错误处理方法、C++异常处理方法(throw, try, catch)简介
该文章介绍了C++异常处理机制,包括C语言错误处理方法、C++异常处理方法(使用throw, try, catch),并给出了具体的代码示例。
s1mba
2017/12/28
1.9K0
从零开始学C++之异常(一):C语言错误处理方法、C++异常处理方法(throw, try, catch)简介
在Android Native层实现Try/Catch异常处理机制
在Native层实现异常处理的关键在于信号处理(Signal Handling)和非局部跳转(Non-Local Jumps)。当程序发生错误(如访问非法内存、除以零等)时,操作系统会向进程发送一个信号。我们可以设置一个信号处理函数(Signal Handler),在收到信号时执行特定的代码。
陆业聪
2024/07/23
3980
在Android Native层实现Try/Catch异常处理机制
3 年大厂工作经验面试竟然要我手写 atoi 函数
手写代码是面试过程常见的环节之一,但是一般都是手写算法题,此次面试官要我手写一个基本的 C 语言 atoi,内心一惊,这怎么感觉像是校招…
恋喵大鲤鱼
2020/06/18
1.4K0
c标准库总结
学习c语言十几年了,却从来没有完整的将c标准库看一看,我想在这一点上我是欠缺的。作为一个技术人员,无论什么时候都不能忘记自己最擅长的技能,这次借一个偶然的契机,翻一翻c标准库,希望以后自己在技术上越来越牛。
用户7886150
2021/02/25
1.6K0
利用C语言中的setjmp和longjmp,来实现异常捕获和协程
在 C 标准库中,有两个威力很猛的函数:setjmp 和 longjmp,不知道各位小伙伴在代码中是否使用过?我问了身体的几位同事,一部分人不认识这两个函数,有一部分人知道这个函数,但从来没有使用过。
IOT物联网小镇
2021/05/13
2.2K1
利用C语言中的setjmp和longjmp,来实现异常捕获和协程
【C指针(五)】6种转移表实现整合longjmp()/setjmp()函数和qsort函数详解分析&&模拟实现
本小节,我们将继续学习C语言转移表,什么是回调函数,回调函数又是什么?qsort函数怎么使用,怎么理解处理,要注意的细节,当然qsort使用举例,最后我们进行qsort函数的模拟实现!文章干货满满,走起!
学习起来吧
2024/02/29
5450
【C指针(五)】6种转移表实现整合longjmp()/setjmp()函数和qsort函数详解分析&&模拟实现
C语言异常处理之 setjmp()和longjmp()
异常处理之除0情况 相信大家处理除0时,都会通过函数,然后判断除数是否为0,代码如下所示: double divide(doublea,double b) { const double delta = 0.00000000001; //由于浮点数不精确,所以需要定义个很小的数 if(!((-delta<b)&&(b<delta))) { return a/b ; } else { return 0;
诺谦
2018/04/16
1.1K0
小白学协程笔记2-c语言实现协程-2021-2-10
前面我们已经了解到协程的基本概念以及对称协程和非对称协程的定义,本节将对如何用c语言在用户态实现协程切换作以简单介绍。
用户7719114
2022/02/22
1.5K0
小白学协程笔记2-c语言实现协程-2021-2-10
C语言函数大全--l开头的函数
上述示例程序中,首先通过 open() 函数打开一个名为 test.txt 的文件,并设置文件访问模式为可读写。接着,调用 lock() 函数对该文件进行加锁操作,保护写入数据的过程。然后,通过 write() 函数将数据写入到文件中。最后,调用 lock() 函数对该文件进行解锁操作,释放锁定的资源。
huazie
2025/04/29
1480
C语言函数大全--l开头的函数
C语言参考手册pdf
《C语言参考手册(原书第5版)》是C语言的详尽参考手册,分为两部分:第一部分讨论了C语言的所有语言特征,包括词法、预处理机制、声明、类型、表达式、语句以及函数等基本语言特征;第二部分讨论了C语言的标准库,针对它们不同的功能分别进行详细介绍。为了帮助读者理解相关概念,《C语言参考手册(原书第5版)》在讨论C语言及其标准库的细节时,提供了许多实例和解释。第一部分的各章还提供了练习题和主要练习的解答,这些练习可帮助读者加深理解C语言的基本功能和特性。
用户7886150
2021/02/11
2.9K0
C语言函数大全--j开头的函数
J_0(x) = \sum_{m=0}^{\infty} \frac{(-1)^m}{(m!)^2} \left( \frac{x}{2} \right)^{2m}
huazie
2025/04/23
1820
C语言函数大全--j开头的函数
C语言中常用的标准库函数有哪些?
文件<stddef.h>里包含了标准库的一些常用定义,无论我们包含哪个标准头文件,<stddef.h>都会被自动包含进来。
MCU起航
2020/07/31
3.6K0
C语言中常用的标准库函数有哪些?
csapp 第八章 异常控制流 读书笔记
的过渡称为控制转移(control transfer)。这样的控制转移序列叫做处理器的控制流(flow of control or control flow) control flow的突变(
BBuf
2023/08/22
4260
csapp 第八章 异常控制流 读书笔记
C++异常
注:实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误
用户9645905
2022/11/30
7620
C++异常
C语言——if(0)之后的语句真的不会执行吗?
在执行if语句时,首先会计算表达式的值,如果表达式的值为零,语句不会执行,若非零,则执行语句。由此可见if (0) 表示不执行,if (1)表示要执行。if (x)根据x的值是否为0来决定是否执行,他等价于if (x != 0)。
用户4645519
2021/12/30
2.1K0
C++异常的介绍和分析
C++11:异常 1.C语言传统的处理错误的方式 与 C++的对比 传统的错误处理机制: 终止程序,如assert,缺陷:用户难以接受。如发生内存错误,除0错误时就会终止程序。 返回错误码,缺陷:需要程序员自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码 放到errno中,表示错误 C 标准库中setjmp和longjmp组合。这个不是很常用,了解一下 实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误。 c++处理方式 使
雪芙花
2022/10/31
8730
C++异常的介绍和分析
[apue] 进程环境那些事儿
众所周知,main 函数为 unix like 系统上可执行文件的"入口",然而这个入口并不是指链接器设置的程序起始地址,后者通常是一个启动例程,它从内核取得命令行参数和环境变量值后,为调用 main 函数做好安排。main 函数原型为:
海海
2023/09/01
4170
[apue] 进程环境那些事儿
相关推荐
C++为什么要引入异常处理机制
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档