异常处理

来源:互联网 发布:chart.js饼图显示数值 编辑:程序博客网 时间:2024/04/30 01:31
C++语言异常处理 
Visual C++
提供了对C语言、 C++语言及MFC的支持,因而其涉及到的异常(exception)处理也包含了这三种类型,即C语言、C++语言和MFC的异常处理。除此之外,微软对CC++的异常处理进行了扩展,提出了结构化异常处理(SEH)的概念,它支持CC++(与之相比,MFC异常处理仅支持C++)。 

   一个典型的异常处理包含如下几个步骤: 
   1)程序执行时发生错误 
   2)以一个异常对象(最简单的是一个整数)记录错误的原因及相关信息; 
   3)程序检测到这个错误(读取异常对象); 
   4)程序决定如何处理错误 
   5)进行错误处理,并在此后恢复/终止程序的执行。 
   CC++MFCSEH在这几个步骤中表现出了不同的特点。

C
语言异常处理 

   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_SUCCESSEXIT_FAILURE分别定义为01 

   对于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挂接的函数
    atexit挂接的函数
    atexit挂接的函数

   Visual C++中,如果以abort函数(此函数不带参数,原型为void abort(void))终止程序,则会在debug模式运行时弹出对话框。 
断言(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; 
}  

   在此程序中,为了避免我们的strcpyC库中的strcpy重名,将其改为myStrcpy
失败的断言也会弹出对话框,这是因为_assert()函数中也调用了abort()函数。 
   assert本质上是一个宏,而不是一个函数,因而不能把带有副作用的表达式放入assert"参数"中。 

  3 errno 
   errnoC程序中是一个全局变量,这个变量由C运行时库函数设置,用户程序需要在程序发生异常时检测之。C运行库中主要在math.hstdio.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没有被设置。 

   Visual C++提供了两种版本的C运行时库。-个版本供单线程应用程序调用,另一个版本供多线程应用程序调用。多线程运行时库与单线程运行时库的一个重大差别就是对于类似errno的全局变量,每个线程单独设置了一个。因此,对于多线程的程序,我们应该使用多线程C运行时库,才能获得正确的error值。 
   另外,在使用errno之前,我们最好将其设置为0,即执行errno = 0的赋值语句。 

   4 其它 
   除了上述异常处理方式外,在C语言中还支持非局部跳转(使用setjmplongjmp)、信号(使用 signal raise)、返回错误值或回传错误值给参数等方式进行一定能力的异常处理。 
   从以上分析可知,C语言的异常处理是简单而不全面的。与C++的异常处理比起来,C语言异常处理相形见绌。 

 C++
异常处理语法 
 
标准C++语言中专门集成了异常处理的相关语法(与之不同的是,所有的标准库异常体系都需要运行库的支持,它不是语言内核支持的)。当然,异常处理被加到程序设计语言中,也是程序语言发展和逐步完善的必然结果。C++不是唯一集成异常处理的语言。 

 1  C++
的异常处理结构为: 
try 

//
可能引发异常的代码 

catch(type_1 e) 

// type_1
类型异常处理 

catch(type_2 e) 

// type_2
类型异常处理 

catch (...)//
会捕获所有未被捕获的异常,必须最后出现 

}  

   而异常的抛出方式为使用throw(type e)trycatchthrow都是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),分别抛出intPoint异常。当main函数的try{}中调用f(point)时和f(1)时,分别输出: 

   捕获到Point异常:(0,0) 
   捕获到int异常:

   C++中,throw抛出异常的特点有: 
   1)可以抛出基本数据类型异常,如intchar等; 
   2)可以抛出复杂数据类型异常,如结构体(在C++中结构体也是类)和类; 
   3C++的异常处理必须由调用者主动检查。一旦抛出异常,而程序不捕获的话,那么abort()函数就会被调用,程序被终止; 
   4)可以在函数头后加throw([type-ID-list])给出异常规格,声明其能抛出什么类型的异常。type-ID-list是一个可选项,其中包括了一个或多个类型的名字,它们之间以逗号分隔。如果函数没有异常规格指定,则可以抛出任意类型的异常。 

   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)中有一个空指针

  //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
}

3异常处理函数 
   在标准C++中,还定义了数个异常处理的相关函数和类型(包含在头文件<exception>中): 
namespace std 

  //EH类型 
  class bad_exception; 
  class exception; 

  typedef void (*terminate_handler)(); 
  typedef void (*unexpected_handler)(); 

  // 函数 
  terminate_handler set_terminate(terminate_handler) throw(); 
  unexpected_handler set_unexpected(unexpected_handler) throw(); 

  void terminate(); 
  void unexpected(); 

  bool uncaught_exception(); 
}  

   其中的terminate相关函数与未被捕获的异常有关,如果一种异常没有被指定catch模块,则将导致terminate()函数被调用,terminate()函数中会调用ahort()函数来终止程序。可以通过set_terminate(terminate_handler)函数为terminate()专门指定要调用的函数,例如: 

#include <cstdio> 
#include <exception> 
using namespace std; 
//
定义Point结构体(类) 
typedef struct tagPoint 

  int x; 
  int y; 
} Point; 
//
扔出Point异常的函数 
static void f() 

  Point p; 
  p.x = 0; 
  p.y = 0; 
  throw p; 

//set_terminate
将指定的函数 
void terminateFunc() 

  printf("set_terminate指定的函数/n"); 


int main() 

  set_terminate(terminateFunc); 
  try 
  { 
   f(); //抛出Point异常 
  } 
  catch (int) //捕获int异常 
  { 
   printf("捕获到int异常"); 
  } 
  //Point将不能被捕获到,引发terminateFunc函数被执行 

  return 0; 
}  

   这个程序将在控制台上输出 "set_terminate指定的函数字符串,因为Point类型的异常没有被捕获到。当然,它也会弹出图1所示对话框(因为调用了abort()函数)。 

   上述给出的仅仅是一个set_terminate指定函数的例子。在实际工程中,往往使用set_terminate指定的函数进行一些清除性的工作,其后再调用exit(int)函数终止程序。这样,abort()函数就不会被调用了,也不会输出对话框。 

MFC
异常处理 
   MFC中异常处理的语法和语义构建在标准C++异常处理语法和语义的基础之上,其解决方案为: 
   MFC异常处理 = MFC 异常处理类   
 1
 
   MFC定义了TRYCATCH(及AND_CATCHEND_CATCH)和THROW(及THROW_LAST)等用于异常处理的宏,其本质上也是标准C++trycatchthrow的进一步强化,由这些宏的定义可知: 
#ifndef _AFX_OLD_EXCEPTIONS 

#define TRY { AFX_EXCEPTION_LINK _afxExceptionLink; try { 

#define CATCH(class, e) } catch (class* e) / 
{ ASSERT(e->IsKindOf(RUNTIME_CLASS(class))); / 
_afxExceptionLink.m_pException = e; 

#define AND_CATCH(class, e) } catch (class* e) / 
{ ASSERT(e->IsKindOf(RUNTIME_CLASS(class))); / 
_afxExceptionLink.m_pException = e; 

#define END_CATCH } } 

#define THROW(e) throw e 
#define THROW_LAST() (AfxThrowLastCleanup(), throw) 

// Advanced macros for smaller code 
#define CATCH_ALL(e) } catch (CException* e) / 
{ { ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); / 
_afxExceptionLink.m_pException = e; 

#define AND_CATCH_ALL(e) } catch (CException* e) / 
{ { ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); / 
_afxExceptionLink.m_pException = e; 

#define END_CATCH_ALL } } } 

#define END_TRY } catch (CException* e) / 
{ ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); / 
_afxExceptionLink.m_pException = e; } }  
   这些宏在使用语法上,有如下特点: 
   1)用TRY 块包含可能产生异常的代码; 
   2)用CATCH块检测并处理异常。要注意的是,CATCH块捕获到的不是异常对象,而是指向异常对象的指针。此外,MFC靠动态类型来辨别异常对象; 
   3)可以在一个TRY 块上捆绑多个异常处理捕获块,第一次捕获使用宏CATCH,以后的使用AND_CATCH,而END_CATCH则用来结束异常捕获队列; 
   4)在异常处理程序内部,可以用THROW_LAST 再次抛出最近一次捕获的异常。 
 
 2 MFC 异常处理类 
   MFC较好地将异常封装到CException类及其派生类中,自成体系,下表给出了MFC 提供的预定义异常: 
异常类  含义  
CMemoryException  
内存不足  
CFileException  
文件异常  
CArchiveException  
存档/序列化异常  
CNotSupportedException  
响应对不支持服务的请求  
CResourceException  Windows 
资源分配异常  
CDaoException  
数据库异常(DAO 类)  
CDBException  
数据库异常(ODBC 类)  
COleException  OLE 
异常  
COleDispatchException  
调度(自动化)异常  
CUserException  
用消息框警告用户然后引发一般 CException 的异常  

   标准C++的异常处理可以处理任意类型的异常,而上节的MFC 宏则只能处理CException 的派生类型,下面我们看一个CFileException的使用例子: 

#include <iostream.h> 
#include "afxwin.h" 

int main() 

  TRY 
  { 
   CFile f( "d://1.txt", CFile::modeWrite ); 
  } 
  CATCH( CFileException, e ) 
  { 
   if( e->m_cause == CFileException::fileNotFound ) 
    cout << "ERROR: File not found/n" << endl; 
}
}
要想这个程序能正确地执行,我们可以在第一个__try块的外面再套一个__try块和一个接收filter-expression返回值为EXCEPTION_EXECUTE_HANDLER__except块,程序改为: 

#include "stdio.h" 

void main() 

  int* p = NULL; // 定义一个空指针 
  puts("SEH begin"); 
  __try 
  { 
   __try 
   { 
    puts("in try"); 
    __try 
    { 
     puts("in try"); 
     *p = 0; // 引发一个内存访问异常 
    } 
    __finally 
    { 
     puts("in finally"); 
    } 
   } 
   __except(puts("in filter"), 0) 
   { 
    puts("in except"); 
   } 
  } 
  __except(puts("in filter"), 1) 
  { 
   puts("in except"); 
  } 
  puts("SEH end"); 
}  

   程序输出: 
SEH begin 
in try //执行__try 
in try //
执行嵌入的__try 
in filter1 //
执行filter-expression,返回EXCEPTION_CONTINUE_SEARCH 
in filter2 //
执行filter-expression,返回EXCEPTION_EXECUTE_HANDLER 
in finally //
展开嵌入的__finally 
in except2 //
执行对应的__except 
SEH end //
处理完毕  
   由此可以看出,因为第一个__exceptfilter-expression返回EXCEPTION_CONTINUE_SEARCH 的原因,"in except1"没有被输出。程序之所以没有崩溃,是因为最终碰到了接收EXCEPTION_EXECUTE_HANDLER的第2__except 

   SEH使用复杂的地方在于较难控制异常处理的流动方向,弄不好程序就""了。如果把例4-1中的__except(puts("in filter"), 1)改为__except(puts("in filter"), -1),程序会进入一个死循环,输出: 

SEH begin 
in try //
执行__try 
in try //
执行嵌入的__try 
in filter //
执行filter-expression,返回EXCEPTION_CONTINUE_EXECUTION 
in filter 
in filter 
in filter 
in filter 
//疯狂输出"in filter"  
   最后疯狂地输出"in filter",我们把断点设置在__except(puts("in filter"), -1)语句之前,按F5会不断进入此断点。 
 各种异常处理的对比 
   下表给出了从各个方面对这本文所给出的Visual C++所支持的四种异常处理进行的对比: 
    
异常处理  支持语言  是否标准  复杂度  推荐使用  
       C
异常处理  C语言  标准C  简单  推荐  
       C++
异常处理  C++语言  标准C++  较简单  推荐  
       MFC
异常处理  C++语言  仅针对MFC程序  较简单  不推荐  
       SEH
异常处理  CC++语言  仅针对Microsoft编译环境  较复杂  不推荐  
 
 
原创粉丝点击