热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C++中的异常处理技术及个人理解(1)

一、简介一个exception(或者是criticalerror,或者是crash)通常意味着你的程序停止正常工作,需要停止执行。例如,由于序访问了无

一、简介

一个 exception ( 或者是critical error, 或者是crash) 通常意味着你的程序停止正常工作,需要停止执行。例如,由于序访问了无效的内存地址(例如NULL指针),无法分配内存缓冲区(内存不足),C 运行时库 (CRT) 检测到错误并请求程序,可能会发生异常终止等情况。

C++ 程序可以处理多种类型的异常:

  • 通过操作系统的结构化异常处理机制产生的 SEH 异常;
  • 由 C 运行时库产生的 CRT 错误;
  • 信号

每种错误类型都需要配置一个异常处理函数来拦截异常并执行一些错误恢复操作。

如果应用程序有多个执行线程,事情可能会更加复杂。一些异常处理程序适用于整个进程,但有些仅适用于当前线程。所以必须在每个线程中安装异常处理程序。

应用程序中的每个模块(EXE 或 DLL)都链接到 CRT 库(静态或动态)。异常处理技术强烈依赖于 CRT 链接类型。

错误类型的多样性、多线程程序中处理异常的差异以及异常处理对 CRT 链接的依赖性需要大量的工作来准确处理您的应用程序允许处理的所有异常。

二、关于异常

异常或严重错误通常意味着程序停止正常工作并需要停止执行。主要有以下原因造成:

  • 程序访问了无效的内存地址(例如 NULL 指针)
  • 由于无限递归导致堆栈溢出
  • 大块数据写入小缓冲区
  • 调用 C++ 类的纯虚方法
  • 无法分配内存缓冲区(内存不足)
  • 将无效参数传递给 C++ 系统函数
  • C 运行时库检测错误并请求程序终止

主要有两种性质不同的异常:SEH 异常(结构化异常处理,SEH)和类型化 C++ 异常


1、SEH异常

操作系统提供了结构化异常处理机制,这意味着所有 Windows 应用程序都可以引发和处理 SEH 异常。SEH 异常最初是为 C 语言设计的,但它们也可以在 C++ 中使用。

SEH 异常使用__try{}__except(){}构造处理。

代码示例:

int* p = NULL; // pointer to NULL
__try
{// Guarded code*p = 13; // causes an access violation exception
}
__except(EXCEPTION_EXECUTE_HANDLER) // Here is exception filter expression
{ // Here is exception handler// Terminate programExitProcess(1);
}

2、C++ 类型异常

C++ 类型异常机制由 C 运行时库提供,这意味着只有 C++ 应用程序可以引发和处理此类异常。C++ 类型异常使用try{} catch{}构造处理。

代码示例:

// exceptions
#include
using namespace std;
int main () {try{throw 20;}catch (int e){cout <<"An exception occurred. Exception Nr. " <}

三、结构化异常处理&#xff08;SEH&#xff09;

当发生 SEH 异常时&#xff0c;您通常会看到如下图所示的的一个窗口&#xff0c;该窗口提供向 Microsoft 发送错误报告。可以使用RaiseException()函数模拟 SEH 异常。

在这里插入图片描述

每个 SEH 异常都有一个关联的异常代码。

为了拉取异常信息&#xff0c;可以在__except()的参数中&#xff0c;使用GetExceptionCode()函数提取语句内部的异常代码&#xff0c;使用GetExceptionInformation()函数提取语句内部的异常信息。但是要使用这些内部函数&#xff0c;通常需要创建自定义异常过滤器。

处理流程图&#xff1a;

在这里插入图片描述

示例代码&#xff1a;

//SEH
int seh_filter(unsigned int code, struct _EXCEPTION_POINTERS* ep)
{// Generate error report// Execute exception handlerreturn EXCEPTION_EXECUTE_HANDLER;
}
void main()
{__try{// .. some buggy code here}__except(seh_filter(GetExceptionCode(), GetExceptionInformation())){ // Terminate programExitProcess(1);}
}

__try{}__except(){}构造主要是面向 C 的。但是可以将 SEH 异常重定向到 C&#43;&#43; 类型异常并像处理 C&#43;&#43; 类型异常一样处理它。

可以使用_set_se_translator()C&#43;&#43; 运行时库 (CRT) 提供的函数来完成。

// crt_settrans.cpp
// compile with: /EHa
#include
#include
#include
void SEFunc();
void trans_func( unsigned int, EXCEPTION_POINTERS* );
class SE_Exception
{
private:unsigned int nSE;
public:SE_Exception() {}SE_Exception( unsigned int n ) : nSE( n ) {}~SE_Exception() {}unsigned int getSeNumber() { return nSE; }
};
int main( void )
{try{_set_se_translator( trans_func );SEFunc();}catch( SE_Exception e ){printf( "Caught a __try exception with SE_Exception.\n" );}
}
void SEFunc()
{__try{int x, y&#61;0;x &#61; 5 / y;}__finally{printf( "In finally\n" );}
}
void trans_func( unsigned int u, EXCEPTION_POINTERS* pExp )
{printf( "In trans_func.\n" );throw SE_Exception();
}

【缺点】

__try{}__catch(Expression){}构造的缺点是可能忘记保护可能导致程序无法处理的异常的潜在不正确代码。

【改进】

可以使用与SetUnhandledExceptionFilter()函数一起设置的顶级未处理异常过滤器来捕获此类未处理的 SEH 异常。

异常信息&#xff08;异常发生前的CPU状态&#xff09;通过EXCEPTION_POINTERS结构体传递给异常处理程序。

代码示例:

LONG WINAPI MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionPtrs)
{// Do something, for example generate error report//..// Execute default exception handler nextreturn EXCEPTION_EXECUTE_HANDLER;
}
void main()
{ SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);// .. some unsafe code here
}

【缺点】

  • 在发生异常的线程上下文中调用顶级 SEH 异常处理程序&#xff0c;会影响异常处理程序从某些异常&#xff08;例如无效堆栈&#xff09;中恢复的能力&#xff1b;
  • 如果异常处理函数位于 DLL 内&#xff0c;则在使用该SetUnhandledExceptionFilter()函数时应小心。如果 DLL 在崩溃时被卸载&#xff0c;则行为可能无法预测。


四、向量异常处理

向量异常处理 (VEH) 是结构化异常处理&#xff08;SEH&#xff09;的扩展。它是在 Windows XP 中引入的。

要添加向量异常处理程序&#xff0c;可以使用AddVectoredExceptionHandler()函数。

缺点是 VEH 仅在 Windows XP 及更高版本中可用&#xff0c;因此AddVectoredExceptionHandler()应在运行时检查函数是否存在。

  • VEH 允许监视或处理应用程序的所有 SEH 异常。为了保持向后兼容性&#xff0c;当程序的某些部分发生 SEH 异常时&#xff0c;系统会依次调用已安装的 VEH 处理程序&#xff0c;然后搜索通常的 SEH 处理程序。
  • VEH 的一个优点是能够链接异常处理程序&#xff0c;因此如果在上层安装了向量异常处理程序&#xff0c;仍然可以拦截异常。

当需要像调试器一样监视_all_SEH 异常时&#xff0c;向量异常处理是合适的。但问题是必须决定要处理哪些异常以及跳过哪些异常。

在程序的代码中&#xff0c;某些异常可能会被__try{}__except(){}构造有意地保护&#xff0c;并且通过在 VEH 中处理此类异常而不将其传递给基于帧的 SEH 处理程序&#xff0c;因此可能会在应用程序逻辑中引入错误。

【注】关于回调函数的选取

SetUnhandledExceptionFilter()函数比 VEH 更适合异常处理&#xff0c;因为它是顶级 SEH 处理程序。如果未处理异常&#xff0c;则调用顶级 SEH 处理程序&#xff0c;无需决定是否应跳过异常。


推荐阅读
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 如何在跨函数中使用内存?
    本文介绍了在跨函数中使用内存的方法,包括使用指针变量、动态分配内存和静态分配内存的区别。通过示例代码说明了如何正确地在不同函数中使用内存,并提醒程序员在使用动态分配内存时要手动释放内存,以防止内存泄漏。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
author-avatar
永恒多一天_313
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有