码迷,mamicode.com
首页 > 编程语言 > 详细

C/C++异常处理机制

时间:2015-03-15 10:47:01      阅读:160      评论:0      收藏: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_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模式运行时弹出DEBUG调试对话框。 

 

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; 

}  

 在此程序中,为了避免我们的strcpyC库中的strcpy重名,将其改为myStrcpy 断言失败,这是因为_assert()函数中也调用了abort()函数。 

 一定要记住的是assert本质上是一个宏,而不是一个函数,因而不能把带有副作用的表达式放入assert"参数"中。 

1.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没有被设置。 

 

2.1 C++异常处理语法 

 

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

   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()函数就会被调用,弹出如图1所示的对话框,程序被终止; 

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

  //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++异常处理机制

标签:

原文地址:http://www.cnblogs.com/ljygoodgoodstudydaydayup/p/4338865.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!