Loading [MathJax]/jax/input/TeX/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >C/C++ 学习笔记八(断言与异常处理)

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

原创
作者头像
Celebi
修改于 2017-08-28 02:08:48
修改于 2017-08-28 02:08:48
2.7K00
代码可运行
举报
文章被收录于专栏: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
在Android Native层实现Try/Catch异常处理机制
在Native层实现异常处理的关键在于信号处理(Signal Handling)和非局部跳转(Non-Local Jumps)。当程序发生错误(如访问非法内存、除以零等)时,操作系统会向进程发送一个信号。我们可以设置一个信号处理函数(Signal Handler),在收到信号时执行特定的代码。
陆业聪
2024/07/23
4810
在Android Native层实现Try/Catch异常处理机制
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
利用C语言中的setjmp和longjmp,来实现异常捕获和协程
在 C 标准库中,有两个威力很猛的函数:setjmp 和 longjmp,不知道各位小伙伴在代码中是否使用过?我问了身体的几位同事,一部分人不认识这两个函数,有一部分人知道这个函数,但从来没有使用过。
IOT物联网小镇
2021/05/13
2.2K1
利用C语言中的setjmp和longjmp,来实现异常捕获和协程
从零开始学C++之异常(一):C语言错误处理方法、C++异常处理方法(throw, try, catch)简介
该文章介绍了C++异常处理机制,包括C语言错误处理方法、C++异常处理方法(使用throw, try, catch),并给出了具体的代码示例。
s1mba
2017/12/28
1.9K0
从零开始学C++之异常(一):C语言错误处理方法、C++异常处理方法(throw, try, catch)简介
C程序设计的异常处理
大家新年好,感谢大家对本公众号一如既往地支持,后面争取创作出更加优质的文章。今天是2021年的第一篇文章,分享一下在C程序设计当中对异常的处理。主要是介绍一下goto和longjmp函数的使用。
飞哥
2021/01/18
8050
C语言参考手册pdf
《C语言参考手册(原书第5版)》是C语言的详尽参考手册,分为两部分:第一部分讨论了C语言的所有语言特征,包括词法、预处理机制、声明、类型、表达式、语句以及函数等基本语言特征;第二部分讨论了C语言的标准库,针对它们不同的功能分别进行详细介绍。为了帮助读者理解相关概念,《C语言参考手册(原书第5版)》在讨论C语言及其标准库的细节时,提供了许多实例和解释。第一部分的各章还提供了练习题和主要练习的解答,这些练习可帮助读者加深理解C语言的基本功能和特性。
用户7886150
2021/02/11
2.9K0
c标准库总结
学习c语言十几年了,却从来没有完整的将c标准库看一看,我想在这一点上我是欠缺的。作为一个技术人员,无论什么时候都不能忘记自己最擅长的技能,这次借一个偶然的契机,翻一翻c标准库,希望以后自己在技术上越来越牛。
用户7886150
2021/02/25
1.7K0
C语言进阶——字符串&&内存函数
  这是牛客网上的一道简单题:判断输入字符是否为字母,一般的解决方法是通过ASCII码判断,不过这样做的话判断表达式较长,此时我们可以利用C语言中的库函数isalpha(判断是否为字母) 来完成这个题目,不仅代码量少,而且通俗易懂。要实现这种效果,就需要学习C语言中的各种库函数,而本文会列出大多数字符串函数和内存函数的使用及其实现,如果你想学习C语言库函数或对字符串、内存有好奇之心,不妨仔细来看看吧!🎉🎉🎉
北 海
2023/07/01
5740
C语言进阶——字符串&&内存函数
c++ 常用函数
#include <strstrea.h>   //该类不再支持,改用<sstream>中的stringstream
用户7886150
2021/02/05
7290
3 年大厂工作经验面试竟然要我手写 atoi 函数
手写代码是面试过程常见的环节之一,但是一般都是手写算法题,此次面试官要我手写一个基本的 C 语言 atoi,内心一惊,这怎么感觉像是校招…
恋喵大鲤鱼
2020/06/18
1.4K0
C/C++错误信息
在 C/C++ 编程中,错误信息的捕获和处理是保证程序健壮性的重要部分。错误通常通过函数的返回值或者全局变量 errno 来表示。为了方便调试和错误处理,C/C++ 提供了多种函数和方法来获取和输出错误信息。以下是 C/C++ 错误处理的常见方法及函数介绍:
ljw695
2025/03/22
2780
【C语言】超详解strncpy&&strncat&&strncmp&&strerror&&perror的使⽤和模拟实现
本小节,阿森继续和你一起学习5个字符串函数:strncpy,strcnat,strncmp的使用和两种模拟实现方法,他们和strcpy等函数比较多了一个n ,实现方法有很大区别,还有strerror和perror的使用,学习这些库函数,可以更好的方便操作字符和字符串,文章干货满满,接下来我们就学习一下这些函数吧!
学习起来吧
2024/02/29
6250
【C语言】超详解strncpy&&strncat&&strncmp&&strerror&&perror的使⽤和模拟实现
csapp 第八章 异常控制流 读书笔记
的过渡称为控制转移(control transfer)。这样的控制转移序列叫做处理器的控制流(flow of control or control flow) control flow的突变(
BBuf
2023/08/22
4680
csapp 第八章 异常控制流 读书笔记
C语言函数大全--l开头的函数
上述示例程序中,首先通过 open() 函数打开一个名为 test.txt 的文件,并设置文件访问模式为可读写。接着,调用 lock() 函数对该文件进行加锁操作,保护写入数据的过程。然后,通过 write() 函数将数据写入到文件中。最后,调用 lock() 函数对该文件进行解锁操作,释放锁定的资源。
huazie
2025/04/29
1780
C语言函数大全--l开头的函数
[apue] 进程环境那些事儿
众所周知,main 函数为 unix like 系统上可执行文件的"入口",然而这个入口并不是指链接器设置的程序起始地址,后者通常是一个启动例程,它从内核取得命令行参数和环境变量值后,为调用 main 函数做好安排。main 函数原型为:
海海
2023/09/01
4420
[apue] 进程环境那些事儿
【C语言】字符串函数「超详细」
🚩write in front🚩 🔎大家好,我是謓泽,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎 🏅2021年度博客之星物联网与嵌入式开发TOP5~2021博客之星Top100~阿里云专家^ 星级博主~掘金⇿InfoQ创作者~周榜54»总榜2999🏅 🆔本文由 謓泽 原创 CSDN首发 🙉 如需转载还请通知⚠ 📝个人主页-謓泽的博客_CSDN博客💬 🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​ 📣系列专栏-【C】系列_謓泽的博客-CSDN博客🎓 📢本文 de 创作时间  ⇨
謓泽
2022/12/12
1.5K0
【C语言】字符串函数「超详细」
C语言——if(0)之后的语句真的不会执行吗?
在执行if语句时,首先会计算表达式的值,如果表达式的值为零,语句不会执行,若非零,则执行语句。由此可见if (0) 表示不执行,if (1)表示要执行。if (x)根据x的值是否为0来决定是否执行,他等价于if (x != 0)。
用户4645519
2021/12/30
2.2K0
strtol函數的用法
相关函数: atof, atoi, atol, strtod, strtoul 表头文件: #include <stdlib.h> 定义函数: long int strtol(const char *nptr, char **endptr, int base) 函数说明: strtol()会将参数nptr字符串根据参数base来转换成长整型数。参数   base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10   进制(字符串以10进制表示),若base值为16则采用16进制(字符串以16进制表示)   。当base值为0时则是采用10进制做转换,但遇到如''0x''前置字符则会使用16进   制做转换。一开始strtol()会扫描参数nptr字符串,跳过前面的空格字符,直到   遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时(''\0'')结束   转换,并将结果返回。若参数endptr不为NULL,则会将遇到不合条件而终止的   nptr中的字符指针由endptr返回。 返回值:    返回转换后的长整型数,否则返回ERANGE并将错误代码存入errno中   。 附加说明: ERANGE指定的转换字符串超出合法范围。     将字符串a, b, c 分别采用10, 2, 16进制转换成数字 ------------------------------------------------ #include <stdlib.h> #include <stdio.h> main() {      char a[] = "100";      char b[] = "100";      char c[] = "ffff";      printf("a = %d\n", strtol(a, NULL, 10)); //100      printf("b = %d\n", strtol(b, NULL, 2));    //4      printf("c = %d\n", strtol(c, NULL, 16)); //65535 }   "100" ---> 100 (Dec) ---> 100 (Dec) "100" ---> 100 (BIN) ---> 4    (Dec) "ffff"---> ffff(Hex) ---> 65535(Dec)
用户7886150
2021/02/15
6930
【C指针(五)】6种转移表实现整合longjmp()/setjmp()函数和qsort函数详解分析&&模拟实现
本小节,我们将继续学习C语言转移表,什么是回调函数,回调函数又是什么?qsort函数怎么使用,怎么理解处理,要注意的细节,当然qsort使用举例,最后我们进行qsort函数的模拟实现!文章干货满满,走起!
学习起来吧
2024/02/29
5790
【C指针(五)】6种转移表实现整合longjmp()/setjmp()函数和qsort函数详解分析&&模拟实现
相关推荐
C++为什么要引入异常处理机制
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档