C/C++异常处理总结

1.C的异常处理

c语言异常处理使用较少,给出具体事例代码:

c语言异常处理的结构体 jmp_buf

typedef struct
{
    unsigned j_sp;  // 堆栈指针寄存器
    unsigned j_ss;  // 堆栈段
    unsigned j_flag;  // 标志寄存器
    unsigned j_cs;  // 代码段
    unsigned j_ip;  // 指令指针寄存器
    unsigned j_bp; // 基址指针
    unsigned j_di;  // 目的指针
    unsigned j_es; // 附加段
    unsigned j_si;  // 源变址
    unsigned j_ds; // 数据段
} jmp_buf;

示例代码:(通过宏的方式进行c++的模拟)

#include"stdio.h"
#include"conio.h"
#include"setjmp.h"
jmp_buf Jump_Buffer;
#define try if(!setjmp(Jump_Buffer))
#define catch else
#define throw longjmp(Jump_Buffer,1)
int Test(int T)
{
    if(T>100)
        throw;
    else
          puts("OK.");
    return 0;
}
int Test_T(int T)
{
    Test(T);
    return 0;
}
int main()
{
    int T;
    try{
          puts("Input a value:");
          scanf("%d",&T);
          T++;
          Test_T(T);
      } catch{
          puts("Input Error!");
      }
    getch();
    return 0;
}

说明:调用setjmp将当前程序的栈信息保存到jmp_buf并返回0,当程序出错时通过longjmp跳转到jmp_buf的位置并返回错误码1;

1.非常类似于goto,比goto稍微好点,增强了可读性;

2.内存的释放只能通过错误码的方式,程序员自己在“#define catch else”中进行处理;

3.有文章说这种方式应用到c++上会导致栈上类的析构函数不调用,这种对比不科学,C比C++先出来,当然不会去兼容C++;因为C没有gc所以其实对于C而言这已经是非常好的方式了;

2.C++的异常处理

try{
    //do something
    throw string("this is exception");
} catch(const string& e) {
    cout << "catch a exception " << e << endl;
}

c++异常重点说明需要注意的地方:

  • 异常不会对基本类型做转换,下面输出的是c;
try{
    throw 'a';
}catch(int a) {
    cout << "int" << endl;
}catch(char c) {
    cout << "char" << endl;
}
  • 允许从派生类到基类的类型转换

    //基类
    class Base{
        public:
            Base(string msg):m_msg(msg)
            {
            }
            virtual void what(){
                cout << m_msg << endl;
            }
        protected:
            string m_msg;
    };
    //派生类,重新实现了虚函数
    class CBase : public Base
    {
        public:
            CBase(string msg):Base(msg)
            {
    
            }
            void what()
            {
               cout << "CBase:" << m_msg << endl;
            }
            void test()
            {
                cout << "I am a CBase" << endl;
            }
    };
    
    int main()
    {
        try {
            //do some thing
        //抛出派生类对象
            throw CBase("I am a CBase exception");
        }catch(Base& e) {  //使用基类可以接收
            e.what();
        }
    }
    
  • 允许非常量到常量的类型转换,也就是说可以抛出一个非常量类型,然后使用catch捕捉对应的常量类型版本

  • 允许数组被转换为数组指针,允许函数被转换为函数指针

  • 可以使用…捕获所有异常

        try{
            throw Exception("I am a exception");    
        }catch(...) {
            //log the exception
            throw;
        }
    
  • 默认异常处理函数,set_terminate函数可以用来设置自己的terminate函数

    #include <exception>
    #include <iostream>
    #include <cstdlib>
    using namespace std;
    
    class MyError {
        const char* const data;
    public:
        MyError(const char* const msg = 0):data(msg)
        {
            //idle
        }
    };
    
    void do_error() {
        throw MyError("something bad happend");
    }
    //自定义的terminate函数,函数原型需要一致
    void terminator()
    {
        cout << "I'll be back" << endl;
        exit(0);
    }
    
    int main()
    {
        //设置自定义的terminate,返回的是原有的terminate函数指针
        void (*old_terminate)() = set_terminate(terminator);
        do_error();
    }
    上面的代码会输出I'll be back
    
  • 标准异常类

C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:

C++ 异常的层次结构

下表是对上面层次结构中出现的每个异常的说明:

异常描述
std::exception该异常是所有标准 C++ 异常的父类。
std::bad_alloc该异常可以通过 new 抛出。
std::bad_cast该异常可以通过 dynamic_cast 抛出。
std::bad_exception这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid该异常可以通过 typeid 抛出。
std::logic_error理论上可以通过读取代码来检测到的异常。
std::domain_error当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument当使用了无效的参数时,会抛出该异常。
std::length_error当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator
std::runtime_error理论上不可以通过读取代码来检测到的异常。
std::overflow_error当发生数学上溢时,会抛出该异常。
std::range_error当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error当发生数学下溢时,会抛出该异常。

定义自己的异常:

#include <iostream>
#include <exception>
using namespace std;
 
struct MyException : public exception
{
  const char * what () const throw ()
  {
    return "C++ Exception";
  }
};
 
int main()
{
  try
  {
    throw MyException();
  }
  catch(MyException& e)
  {
    std::cout << "MyException caught" << std::endl;
    std::cout << e.what() << std::endl;
  }
  catch(std::exception& e)
  {
    //其他的错误
  }
}

3.C++异常的安全处理

注意点:

1.C++异常使用最主要的是需要避免内存泄露,C++只是保证栈上已经构造的对象正常析构,并不能保证非RAII的资源正常析构,也不能保证事务的完整性。其实在没有gc的情况下要保证C++异常使用的安全是一件需要非常小心的事情。

2.C++异常是可以再构造函数抛出,但是该对象的析构函数不会调用。;其实很好理解分配内存就能抛出异常。

3.析构函数不应该抛出异常(如果抛出异常也要自己吃掉)。

C++异常安全的三个层面:

1.no_throw guarantee 不抛出异常使用关键字nothrow标识

2.basic exception safety guarantee 不会产生内存泄露

3.strong exception safety guarantee 事务上的原子性,通过copy swap保证事务的原子性。

其实要实现这三个安全,个人感觉非常难。

4.为什么析构函数不能抛出异常

 class Base
 {
 public:
    void fun()     {   throw 1;    }
    ~Base()        {   throw 2;    }
 };
 
 int main()
 {
   try
   {
       Base base;
      //base.fun();
   }
   catch (...)
   {
       //cout <<"get the catch"<<endl;
   }
 }

如果在base.fun()抛出异常后,在执行catch就会对base进行析构,如果这时析构再抛出异常就不能进行catch,windows平台会直接崩溃。

5.相较于返回值与抛异常的比较

返回值的优势

1.抛异常相对消耗性能,如果需要不断调用的函数使用抛异常的处理方式非常消耗性能,如read,write函数。

2.返回值一般信息单一,用于一些已知,需要直接处理的错误。

异常处理的优势

1.比返回值能带出跟多的信息,返回值一般是个错误码,需要其他函数获取更多信息。

2.抛异常的方式能让代码更加优雅,可以不用像返回错误码需要每一层调用都关注处理,异常可以在需要处理的那一层直接try…catch。

3.抛异常能让程序员提前发现问题,如果有异常必须去处理,否则程序会crash,所以异常的抛出也应该在程序无法正确执行的情况下抛出。

6.windows的SEH异常处理

windows平台VC编译器对异常的处理都会基于windows的SEH系统调用与扩展,所以如果编译的时候开启了SEH,就能扩展捕获如除0的异常,所以windows的异常处理是非常强大的。

这个博客写的非常详细,不过最后的伪代码有些过时,我再vs2019下执行不能通过,PSCOPETABLE pScopeTable的定义应该已经过时

https://blog.csdn.net/chenlycly/article/details/52575260

SEH处理的关键:

1.异常基于线程

2.当出现异常会回调存储在寄存器fs[0]的异常处理函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帝都小鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值