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++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:
下表是对上面层次结构中出现的每个异常的说明:
定义自己的异常:
#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]的异常处理函数。