C/C++异常处理 逃离我推掉我的手 2022-07-28 04:20 228阅读 0赞 [C++为什么抓不到除0错“异常”?][C_0] 1.C语言异常处理 1.1 异常终止 标准C库提供了abort()和exit()两个函数,它们可以强行终止程序的运行,其声明处于<stdlib.h>头文件中。这两个函数本身不能检测异常,但在C程序发生异常后经常使用这两个函数进行程序终止。下面的这个例子描述了exit()的行为: #include <stdio.h> #include <stdlib.h> int main(void) { exit(EXIT_SUCCESS); printf("程序不会执行到这里\n"); return 0; } 在这个例子中,main函数一开始就执行了exit函数(此函数原型为void exit(int)),因此,程序不会输出"程序不会执行到这里"。程序中的exit(EXIT\_SUCCESS)表示程序正常结束,与之对应的exit(EXIT\_FAILURE)表示程序执行错误,只能强行终止。EXIT\_SUCCESS、EXIT\_FAILURE分别定义为0和1。 对于exit函数,我们可以利用atexit函数为exit事件"挂接"另外的函数,这种"挂接"有点类似Windows编程中的"钩子"(Hook)。譬如: #include <stdio.h> #include <stdlib.h> static void atExitFunc(void) { printf("atexit挂接的函数\n"); } int main(void) { atexit(atExitFunc); exit(EXIT_SUCCESS); printf("程序不会执行到这里\n"); return 0; } 程序输出"atexit挂接的函数"后即终止。来看下面的程序,我们不调用exit函数,看看atexit挂接的函数会否执行: #include <stdio.h> #include <stdlib.h> static void atExitFunc(void) { printf("atexit挂接的函数\n"); } int main(void) { atexit(atExitFunc); //exit(EXIT_SUCCESS); printf("不调用exit函数\n"); return 0; } 程序输出: 不调用exit函数 atexit挂接的函数 这说明,即便是我们不调用exit函数,当程序本身退出时,atexit挂接的函数仍然会被执行。 atexit可以被多次执行,并挂接多个函数,这些函数的执行顺序为后挂接的先执行,例如: #include <stdio.h> #include <stdlib.h> static void atExitFunc1(void) { printf("atexit挂接的函数1\n"); } static void atExitFunc2(void) { printf("atexit挂接的函数2\n"); } static void atExitFunc3(void) { printf("atexit挂接的函数3\n"); } int main(void) { atexit(atExitFunc1); atexit(atExitFunc2); atexit(atExitFunc3); return 0; } 输出的结果是: atexit挂接的函数3 atexit挂接的函数2 atexit挂接的函数1 在Visual C++中,如果以abort函数(此函数不带参数,原型为void abort(void))终止程序,则会在debug模式运行时弹出DEBUG调试对话框。 按照ISO C的规定,一个进程可以登记多达32个函数,这些函数将由exit自动调用,通常这32个函数被称为终止处理程序,并调用atexit函数来登记这些函数。 atexit()注册的函数类型应为不接受任何参数的void函数,exit调用这些注册函数的顺序与它们 登记时候的顺序相反。同一个函数如若登记多次,则也会被调用多次。 我们通常认为C语言的起始函数是main函数,实质上一个程序的启动函数并不一定是main函数,这个可以采用链接器来设置,但是gcc中默认main就是C语言的入口函数,在main函数启动之前,内核会调用一个特殊的启动例程,这个启动例程从内核中取得命令行参数值和环境变量值,为调用main函数做好准备,因此对应程序而言main函数并不是起始,但是对应C 语言而言,main函数就是入口地址,其他的链接器帮助我们完成,实际上mian函数的执行是使用了exec函数,这是一个函数族,这也是内核执行一个程序的唯一方法 记得在面试题中有一道关于在main函数退出之后,是否还可以执行程序的问题,这时候就要使用到前面提到的atexit函数.我们知道exit是在main函数调用结束以后调用,因此这些函数的执行肯定在main函数之后,这也是上面面试题的解决方法。即采用atexit函数登记相关的执行函数即可。 exit()和\_exit()以及\_Exit()函数的本质区别是是否立即进入内核,\_exit()以及\_Exit()函数都是在调用后立即进入内核,而不会执行一些清理处理,但是exit()则会执行一些清理处理,这也是为什么会存在atexit()函数的原因,因为exit()函数需要执行清理处理,需要执行一系列的操作,这些终止处理函数实际上就是完成各种所谓的清除操作的实际执行体。 1.2 断言(assert) assert宏在C语言程序的调试中发挥着重要的作用,它用于检测不会发生的情况,表明一旦发生了这样的情况,程序就实际上执行错误了,例如strcpy函数: char *strcpy(char *strDest, const char *strSrc) { char * address = strDest; assert((strDest != NULL) && (strSrc != NULL)); while ((*strDest++ = *strSrc++) != '\0') ; return address; } 其中包含断言assert( (strDest != NULL) && (strSrc != NULL) ),它的意思是源和目的字符串的地址都不能为空,一旦为空,程序实际上就执行错误了,会引发一个abort。 assert宏的定义为: #ifdef NDEBUG #define assert(exp) ((void)0) #else #ifdef __cplusplus extern "C" { #endif _CRTIMP void __cdecl _assert(void *, void *, unsigned); #ifdef __cplusplus } #endif #define assert(exp) (void)( (exp) || (_assert(#exp, __FILE__, __LINE__), 0) ) #endif /* NDEBUG */ 如果程序不在debug模式下,assert宏实际上什么都不做;而在debug模式下,实际上是对\_assert() 函数的调用,此函数将输出发生错误的文件名、代码行、条件表达式。例如下列程序: #include <stdio.h> #include <stdlib.h> #include <assert.h> char * myStrcpy( char *strDest, const char *strSrc ) { char *address = strDest; assert( (strDest != NULL) && (strSrc != NULL) ); while( (*strDest++ = *strSrc++) != '\0' ); return address; } int main(void) { myStrcpy(NULL,NULL); return 0; } 在此程序中,为了避免我们的strcpy与C库中的strcpy重名,将其改为myStrcpy。 断言失败,这是因为\_assert()函数中也调用了abort()函数。 一定要记住的是assert本质上是一个宏,而不是一个函数,因而不能把带有副作用的表达式放入assert的"参数"中。 1.3 errno errno在C程序中是一个全局变量,这个变量由C运行时库函数设置,用户程序需要在程序发生异常时检测之。C运行库中主要在math.h和stdio.h头文件声明的函数中使用了errno,前者用于检测数学运算的合法性,后者用于检测I/O操作中(主要是文件)的错误,例如: #include <errno.h> #include <math.h> #include <stdio.h> int main(void) { errno = 0; if (NULL == fopen("d:\\1.txt", "rb")) { printf("%d", errno); } else { printf("%d", errno); } return 0; } 在此程序中,如果文件打开失败(fopen返回NULL),证明发生了异常。我们读取error可以获知错误的原因,如果D盘根目录下不存在"1.txt"文件,将输出2,表示文件不存在;在文件存在并正确打开的情况下,将执行到else语句,输出0,证明errno没有被设置。 2.1 C++异常处理语法 感谢C++语言的后期改造者们,他们在标准C++语言中专门集成了异常处理的相关语法(与之不同的是,所有的C 标准库异常体系都需要运行库的支持,它不是语言内核支持的)。当然,异常处理被加到程序设计语言中,也是程序语言发展和逐步完善的必然结果。我们看到,C++不是唯一集成异常处理的语言。 C++的异常处理结构为: try \{ //可能引发异常的代码 \} catch(type\_1 e) \{ // type\_1类型异常处理 \} catch(type\_2 e) \{ // type\_2类型异常处理 \} catch (...)//会捕获所有未被捕获的异常,必须最后出现 \{ \} 而异常的抛出方式为使用throw(type e),try、catch和throw都是C++为处理异常而添加的关键字。 看看这个例子: #include <stdio.h> //定义Point结构体(类) typedef struct tagPoint { int x; int y; } Point; //扔出int异常的函数 static void f(int n) { throw 1; } //扔出Point异常的函数 static void f(Point point) { Point p; p.x = 0; p.y = 0; throw p; } int main() { Point point; point.x = 0; point.y = 0; try { f(point); //抛出Point异常 //f(1); //抛出int异常 } catch (int e) { printf("捕获到int异常:%d\n", e); } catch (Point e) { printf("捕获到Point异常:(%d,%d)\n", e.x, e.y); } return 0; } 函数f定义了两个版本:f(int)和f(Point),分别抛出int和Point异常。当main函数的try\{…\}中调用f(point)时和f(1)时,分别输出: 捕获到Point异常:(0,0) 和 捕获到int异常:1 在C++中,throw抛出异常的特点有: (1)可以抛出基本数据类型异常,如int和char等; (2)可以抛出复杂数据类型异常,如结构体(在C++中结构体也是类)和类; (3)C++的异常处理必须由调用者主动检查。一旦抛出异常,而程序不捕获的话,那么abort()函数就会被调用,程序被终止; (4)可以在函数头后加throw(\[type-ID-list\])给出异常规格,声明其能抛出什么类型的异常。type-ID-list是一个可选项,其中包括了一个或多个类型的名字,它们之间以逗号分隔。如果函数没有异常规格指定,则可以抛出任意类型的异常。 2.2 标准异常 下面给出了C++提供的一些标准异常: namespace std \{ //exception派生 class logic\_error; //逻辑错误,在程序运行前可以检测出来 //logic\_error派生 class domain\_error; //违反了前置条件 class invalid\_argument; //指出函数的一个无效参数 class length\_error; //指出有一个超过类型size\_t的最大可表现值长度的对象的企图 class out\_of\_range; //参数越界 class bad\_cast; //在运行时类型识别中有一个无效的dynamic\_cast表达式 class bad\_typeid; //报告在表达试typeid(\*p)中有一个空指针p //exception派生 class runtime\_error; //运行时错误,仅在程序运行中检测到 //runtime\_error派生 class range\_error; //违反后置条件 class overflow\_error; //报告一个算术溢出 class bad\_alloc; //存储分配错误 \} 请注意观察上述类的层次结构,可以看出,标准异常都派生自一个公共的基类exception。基类包含必要的多态性函数提供异常描述,可以被重载。下面是exception类的原型: class exception \{ public: exception() throw(); exception(const exception& rhs) throw(); exception& operator=(const exception& rhs) throw(); virtual ~exception() throw(); virtual const char \*wh; \}; 关于标准C++的异常处理,还包含一些比较复杂的技巧和内容,我们可以查阅《more effective C++》的条款9~条款15。 [C_0]: http://blog.csdn.net/nanyu/article/details/6475555
相关 异常处理 1、异常与错误 错误对于程序而言是致命的,运用java的异常处理机制,异常发生后经过处理,程序还是能正常运行的。如:数组越界异常、除数为0异常等。异常类是指Exception ╰半夏微凉°/ 2023年08月17日 16:09/ 0 赞/ 137 阅读
相关 异常处理 异常 1.什么是异常 在正常运行程序当中,即使语句或表达式在语法上是正确的,但在尝试执行时,它仍可能会引发错误。 在执行时检测到的错误被称为 “异常”,异常不一定会 ╰半橙微兮°/ 2022年12月30日 07:45/ 0 赞/ 149 阅读
相关 【CC精品教程】ContextCapture(CC)集群处理环境部署图文教程 [《无人机航空摄影测量精品教程》合集目录(Pix4d、CC、EPS、PhotoScan、Inpho)][Pix4d_CC_EPS_PhotoScan_Inpho] ![在这里 以你之姓@/ 2022年10月25日 04:45/ 0 赞/ 434 阅读
相关 异常处理 所谓异常,就是以对象的方式表示一个或一类错误,该异常对象不仅封装了错误信息,还包含了错误发生时的“上下文”信息。传统的错误处理方式是,每当程序调用了某个方法进行了某种操作,程序 Bertha 。/ 2022年06月12日 05:12/ 0 赞/ 310 阅读
相关 异常处理 异常定义: 程序在运行时出现不正常情况。 问题也是现实生活中的一个具体事物,可以通过java类的形式进行描述。并封装成对象。Java对不正常 悠悠/ 2022年06月11日 08:12/ 0 赞/ 239 阅读
相关 异常处理 C++的异常处理机制被称为是不可恢复的:一旦异常被处理,程序的执行就不能够在异常被抛出的地方继续。如果这些 catch 子句不包含返回语句,在catch子句完成它的工作之后,程 忘是亡心i/ 2022年05月23日 03:55/ 0 赞/ 290 阅读
相关 异常处理 异常写入日志 package javaBase.io.others; import java.io.; import java.text.S 妖狐艹你老母/ 2021年12月21日 18:49/ 0 赞/ 420 阅读
相关 异常处理 概念 如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返 系统管理员/ 2021年12月14日 08:49/ 0 赞/ 434 阅读
相关 异常处理 1.异常概述 异常的类型:Error(致命的),Exception(非致命的)以及必检和免检异常。 异常就是一种对象,表示阻止正常进行程序执行的错误或者情况。 异常 待我称王封你为后i/ 2021年11月16日 13:22/ 0 赞/ 468 阅读
相关 异常处理 一、Error java虚拟机无法解决的严重问题。比如:JVM系统内部错误、资源耗尽等严重情况。StackOverflowError。 针对这类错误,一般不编写针对性的代码 痛定思痛。/ 2021年11月16日 10:28/ 0 赞/ 373 阅读
还没有评论,来说两句吧...