C/C++异常处理机制
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调用这些注册函数的顺序与它们 登记时候的顺序相反。同一个函数如若登记多次,则也会被调用多次。
记得在面试题中有一道关于在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/C++异常处理机制的更多相关文章
- Java异常处理机制 try-catch-finally 剖析
Java拥有着强大的异常处理机制,最近初步学习了下,感觉内容还是挺多的,特此来将自己的理解写出来与大家分享. 一. 在Java代码code中,由于使用Myeclipse IDE,可以自动提醒用户哪里有 ...
- JAVA 异常处理机制
主要讲述几点: 一.异常的简介 二.异常处理流程 三.运行时异常和非运行时异常 四.throws和throw关键字 一.异常简介 异常处理是在程序运行之中出现的情况,例如除数为零.异常类(Except ...
- 深入理解java异常处理机制
异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程.Java通 过API中Throwable类的众多子类描述各种不同的 ...
- C++学习笔记27:异常处理机制
一.异常处理机制基础 异常的定义 程序中可以检测的运行不正常的情况 异常处理的基本流程 某段程序代码在执行操作时发生特殊情况,引发一个特定的异常 另一段程序代码捕获该异常并处理它 二.异常的引发 th ...
- C++中的异常处理机制
C++中的捕获异常机制catch参数中实参的类型不同,采取的处理方式则不相同,且与普通的函数调用还不一样,具体表现为当抛出异常throw A()或throw obj时,对象会进行一次额外的对象复制操作 ...
- 16、java中的异常处理机制
异常:就是程序在运行时出现不正常情况.异常由来:问题也是现实生活中一个具体的事物,也可以通过java的类的形式进行描述.并封装成对象. 其实就是java对不正常情况进行描述后的对象体现. 对于问题的划 ...
- Struts——(四)异常处理机制
在通常的情况下,我们得到异常以后,需要将页面导航到一个错误提示的页面,提示错误信息.利用Stuts我们可以采用两种方式处理异常: 1.编程式异常处理 即我们在Action中调用业务逻辑层对象的方法时, ...
- Java面向对象编程之异常处理机制
一:Java的异常处理机制的优点: 1:把各种不同情况的异常情况分类,使用JAVA类来表示异常情况,这种类被称为异常类.把各种异常情况表示成异常类,可以充分的发挥类的可扩展性和可重用性. 2:异常流程 ...
- Java之异常处理机制
来源:深入理解java异常处理机制 2.Java异常 异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程.Java通 ...
- SpringMVC异常处理机制详解[附带源码分析]
目录 前言 重要接口和类介绍 HandlerExceptionResolver接口 AbstractHandlerExceptionResolver抽象类 AbstractHandlerMethodE ...
随机推荐
- 278 First Bad Version 第一个错误的版本
你是产品经理,目前正在领导一个团队开发一个新产品.不幸的是,您的产品的最新版本没有通过质量检查.由于每个版本都是基于之前的版本开发的,所以错误版本之后的所有版本都是不好的.假设你有 n 个版本 [1, ...
- C# 调用非托管函数
C#通过DllImport可以直接调用Windows中的一些功能.C++中已经编写好的一些方法: DllImport所在的名字空间:System.Runtime.InteropServices; Dl ...
- [ POI 2012 ] Letters
\(\\\) \(Description\) 给出两个长度为 \(N\) 的字符串\(S_1,S_2\),且保证两个字符串中每一个字符出现次数相同. 现在一次操作可以交换相邻的两个字符,问将 \(S_ ...
- XAMPP--Apache服务无法启动问题定位及处理
一.问题简述: XAMPP 在使用一段时间后,Apache服务无法启动. 二.详细描述: 安装Xampp服务器套件之后,部署使用正常.一段时间未使用,再次打开时,Apache服务无法启动.错误提示如下 ...
- 生产环境4.3.5jboss内存调优
1.查看jboss的监控工具 http://XXX/jmx-console/htmladaptor 2.查看jvm的监控工具 jdk\bin jvisualvm.exe jmc.exe 3.查看jbo ...
- Java Servlet DAO实践(二)
Java Servlet DAO实践(二) DAO连接类 package com.seller.servlets.dao; import java.sql.*; public class DataBa ...
- HDU_1175_连连看
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1175 大意:连连看规则,只能转两次弯,先输入矩阵0表示没有棋子,正整数表示不同的棋子,然后询问,输入两点坐 ...
- 17Oracle Database 维护
Oracle Database 维护 备份 还原
- linux cp复制文件 直接覆盖
命令: \cp -rf aaaa/* bbbb 复制aaa下的文件到bbb目录
- How To:分析ORACLE监听日志中的IP信息
有时候需要分析出ORACLE日志监听中的IP信息,分享一个组合命令,Linux的shell下运行正常. grep "HOST=.*establish.*\* 0" listener ...