C++编译器如何实现异常处理

译者注:本文在网上已经有几个译本,但都不完整,所以我决定自己把它翻译过来。虽然力求信、雅、达,但鉴于这是我的第一次翻译经历,不足之处敬请谅解并指出。

与传统语言相比,C++的一项革命性创新就是它支持异常处理。传统的错误处理方式经常满足不了要求,而异常处理则是一个极好的替代解决方案。它将正常代码和错误处理代码清晰的划分开来,程序变得非常干净并且容易维护。本文讨论了编译器如何实现异常处理。我将假定你已经熟悉异常处理的语法和机制。本文还提供了一个用于VC++的异常处理库,要用库中的处理程序替换掉VC++提供的那个,你只需要调用下面这个函数:
install_my_handler();

之后,程序中的所有异常,从它们被抛出到堆栈展开(stack unwinding),再到调用catch块,最后到程序恢复正常运行,都将由我的异常处理库来管理。

与其它C++特性一样,C++标准并没有规定编译器应该如何来实现异常处理。这意味着每一个编译器的提供商都可以用它们认为恰当的方式来实现它。下面我会描述一下VC++是怎么做的,但即使你使用其它的编译器或操作系统①,本文也应该会是一篇很好的学习材料。VC++的实现方式是以windows系统的结构化异常处理(SEH)②为基础的。

结构化异常处理—概述

在本文的讨论中,我认为异常或者是被明确的抛出的,或者是由于除零溢出、空指针访问等引起的。当它发生时会产生一个中断,接下来控制权就会传递到操作系统的手中。操作系统将调用异常处理程序,检查从异常发生位置开始的函数调用序列,进行堆栈展开和控制权转移。Windows定义了结构“EXCEPTION_REGISTRATION”,使我们能够向操作系统注册自己的异常处理程序。
struct EXCEPTION_REGISTRATION
{
  EXCEPTION_REGISTRATION* prev;
  DWORD handler;
};
  注册时,只需要创建这样一个结构,然后把它的地址放到FS段偏移0的位置上去就行了。下面这句汇编代码演示了这一操作:

mov FS:[0], exc_regp
  prev字段用于建立一个EXCEPTION_REGISTRATION结构的链表,每次注册新的EXCEPTION_REGISTRATION时,我们都要把原来注册的那个的地址存到prev中。

那么,那个异常回调函数长什么样呢?在excpt.h中,windows定义了它的原形:

EXCEPTION_DISPOSITION (*handler)(
  _EXCEPTION_RECORD *ExcRecord,
  void* EstablisherFrame,
  _CONTEXT *ContextRecord,
  void* DispatcherContext); 
  不要管它的参数和返回值,我们先来看一个简单的例子。下面的程序注册了一个异常处理程序,然后通过除以零产生了一个异常。异常处理程序捕获了它,打印了一条消息就完事大吉并退出了。

#include <iostream>
#include <windows.h>
using std::cout;
using std::endl;
struct EXCEPTION_REGISTRATION
{
  EXCEPTION_REGISTRATION* prev;
  DWORD handler;
};
EXCEPTION_DISPOSITION myHandler(
  _EXCEPTION_RECORD *ExcRecord,
  void * EstablisherFrame,
  _CONTEXT *ContextRecord,
  void * DispatcherContext)
{
  cout << "In the exception handler" << endl;
  cout << "Just a demo. exiting..." << endl;
  exit(0);
  return ExceptionContinueExecution; //不会运行到这
}
int g_div = 0;
void bar()
{
  //初始化一个EXCEPTION_REGISTRATION结构
  EXCEPTION_REGISTRATION reg, *preg =  
  reg.handler = (DWORD)myHandler;
  //取得当前异常处理链的“头”
  DWORD prev;
  _asm
  {
    mov EAX, FS:[0]
    mov prev, EAX
  }
  reg.prev = (EXCEPTION_REGISTRATION*) prev;
  //注册!
  _asm
  {
    mov EAX, preg
    mov FS:[0], EAX
  }
  //产生一个异常
  int j = 10 / g_div; //异常,除零溢出
}
int main()
{
  bar();
  return 0;
}
/*-------输出-------------------
In the exception handler
Just a demo. exiting...
---------------------------------*/
  注意EXCEPTION_REGISTRATION必须定义在栈上,并且必须位于比上一个结点更低的内存地址上,Windows对此有严格要求,达不到的话,它就会立刻终止进程。
  函数和堆栈

堆栈是用来保存局部对象的连续内存区。更明确的说,每个函数都有一个相关的栈桢(stack frame)来保存它所有的局部对象和表达式计算过程中用到的临时对象,至少理论上是这样的。但现实中,编译器经常会把一些对象放到寄存器中以便能以更快的速度访问。堆栈是一个处理器(CPU)层次的概念,为了操纵它,处理器提供了一些专用的寄存器和指令。

图1是一个典型的堆栈,它示出了函数foo调用bar,bar又调用widget时的情景。请注意堆栈是向下增长的,这意味着新压入的项的地址低于原有项的地址。

时间: 2024-09-19 08:16:29

C++编译器如何实现异常处理的相关文章

了解C++异常处理的系统开支

为了在运行时处理异常,程序要记录大量的信息.无论执行到什么地方,程序都必须能够识别出如果在此处抛出异常的话,将要被释放哪一个对象;程序必须知道每一个入口点,以便从try块中退出;对于每一个try块,他们都必须跟踪与其相关的catch子句以及这些catch子句能够捕获的异常类型.这种信息的记录不是没有代价的.确保程序满足异常规格不需要运行时的比较(runtime comparisons),而且当异常被抛出时也不用额外的开销来释放相关的对象和匹配正确的catch字句.但是异常处理确是有代价的,即使你

VC编译器设置(/GR,/EH,/Zc,/Ob)

    VC编译器设置   工欲善其事,必先利其器:要想用VC开发出高效率程序,必须对VC编译器有充分的了解.如果我们已经习惯编译器给我的默认设置,那么现在起开始尝试改变吧!本篇文章(或者后续文章)讲解VC编译器设置选项并没有先后顺序,只是作者在具体开发过程碰见了不同的编译设置,进行深入了解,做此文章记录.也希望读到这篇文章的您有所收获:本文章主要参考MSDN,编译器为VC7.1,作者只是添加一些通俗例子,或者翻译部分英文内容,从而达到对编译器设置的通俗理解.   1:/GR(启用运行时类型信息

J2EE开发过程中的异常处理

j2ee|过程|异常处理 在java里有3种异常类型: 1. 检查型异常,这样的异常继承于Excetpion,就是在编译期间需要检查,如果该异常被throw,那么在该异常所在的method后必须显示的throws,调用该method的地方也必须捕获该异常,否则编译器会抛出异常.ejb里的RemoteException是一个这样的异常. 2. 运行时异常,就是在运行期间系统出现的异常,该类异常继承于RuntimeException,该类异常在编译时系统不进行检查,如NullPointerExcet

Java异常处理的陋习展播

异常处理 你觉得自己是一个Java专家吗?是否肯定自己已经全面掌握了Java的异常处理机制?在下面这段代码中,你能够迅速找出异常处理的六个问题吗? 1 OutputStreamWriter out = ... 2 java.sql.Connection conn = ... 3 try { // ⑸ 4 Statement stat = conn.createStatement(); 5 ResultSet rs = stat.executeQuery( 6 "select uid, name

异常处理优劣观(转载)

异常处理 Java编程中的异常处理是一个很常见的话题了,几乎任何一门介绍性的Java课程都会提到异常处理.不过,我认为很多人其实并没有真正掌握正确处理异常情况的方法和策略,最多也就不过了解个大概,知道点概念.本文就对三种不同程度和质量的Java异常处理进行了讨论,所阐述的处理异常的方式按手法的高下分为:好,不好和恶劣三种.同时向你提供了一些解决这些问题的技巧. 好 异常处理提供了处理程序错误的统一机制.事实上,Java语言通过向调用者提出异常警告的方式而显著地提升了软件开发中的异常处理能力.这种

&amp;gt; 第七章 异常处理(rainbow 翻译) (来自重粒子空间)

<<展现C#>> 第七章 异常处理(rainbow 翻译) 出处:http://www.informit.com/matter/ser0000002 正文: 第七章   异常处理     通用语言运行时(CLR)具有的一个很大的优势为,异常处理是跨语言被标准化的.一个在C#中所引发的异常可以在Visual Basic客户中得到处理.不再有 HRESULTs  或者 ISupportErrorInfo 接口.    尽管跨语言异常处理的覆盖面很广,但这一章完全集中讨论C#异常处理.你

《.net编程先锋C#》第七章 异常处理(转)

编程|异常处理 第七章 异常处理通用语言运行时(CLR)具有的一个很大的优势为,异常处理是跨语言被标准化的.一个在C#中所引发的异常可以在Visual Basic客户中得到处理.不再有 HRESULTs 或者 ISupportErrorInfo 接口.尽管跨语言异常处理的覆盖面很广,但这一章完全集中讨论C#异常处理.你稍为改变编译器的溢出处理行为,接着有趣的事情就开始了:你处理了该异常.要增加更多的手段,随后引发你所创建的异常. 7.1 校验(checked)和非校验(unchecked)语句当

ASP.NET中异常处理使用(详细)

asp.net|异常处理    通用语言运行时(CLR)具有的一个很大的优势为,异常处理是跨语言被标准化的.一个在C#中所引发的异常可以在Visual Basic客户中得到处理.不再有 HRESULTs  或者 ISupportErrorInfo 接口.    尽管跨语言异常处理的覆盖面很广,但这一章完全集中讨论C#异常处理.你稍为改变编译器的溢出处理行为,接着有趣的事情就开始了:你处理了该异常.要增加更多的手段,随后引发你所创建的异常. 7.1  校验(checked)和非校验(uncheck

C++的可移植性和跨平台开发[3]:异常处理

上一个帖子"语法"由于篇幅有限,没来得及聊异常,现在把和异常相关的部 分单独拿出来说一下. ★小心new分配内存失败 早期的老式编译器生成的代码,如果new失败会返回空指针.我当年用的Borland C++ 3.1 似乎就是这样的,现在这种编译器应该不多见了.如果你目前用的编译器还有这种行为,那 你就惨了.你可以考虑重载new操作符来抛出bad_alloc异常,便于进行异常处理. 稍微新式一点的编译器,就不是仅仅返回空指针了.当new操作符发现内存告急,按照标 准的规定(参见03标准1