1、函数调用链
函数与子函数之间的调用关系可以用一个链来表示,调用连,相关的一个重要数据结构就是栈帧,子函数返回的时候,栈展开,构造在栈上的对象被编译器设置的析构函数析构销毁。
函数调用 A ----------------------> B ---------------------> C ------------------------->D
2、异常处理链
类似的,windows上的结构化异常处理也有一个类似的异常处理链,而且这个链刚好也是在栈上构造的,但是和函数正常的链是各自独立的。当某一个子函数发生异常的时候,就不能走正常的调用连返回,进而析构对象;这个时候,需要走异常链来回溯,展开异常调用链,所有附着在异常链上的对象,在异常链的第二次调用(unwind处理,第一次是搜索异常处理者)的时候析构。
异常链 ExcpNode_A-------> ExcpNode_B------------------------------------------->!!BOMB!!<
每一个节点都包含了该函数对应的对象的析构操作,正常调用链也好,异常链也好,都是这样的,这些是由C++编译器完成的。每一个call操作,c++编译器产生一个子函数栈帧节点。每一个try-catch语句,c++语句构造一个异常链节点。
但是,如果一个程序员在C函数里面没有写try-catch,但是D函数里面有try-catch语句,而且运行的时候,在D函数里面发生了异常,那么C函数里面的生成的所有对象得不到析构的机会。
3、测试程序代码
// debugee.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
class Object
{
public:
Object(const char tag) : m_tag(tag)
{printf("Object [%c] %p\n", m_tag, this);};
~Object()
{printf("~Object [%c] %p\n", m_tag, this);};
private:
char m_tag;
};
void D(void)
{
Object d('D');
*(int *)0 = 0;
}
void C(void)
{
Object c('C');
D();
}
void B(void)
{
Object b('B');
try
{
C();
}catch(...)
{
printf("B caught exception.\n");
}
}
void A(void)
{
Object a('A');
try
{
B();
}catch(...)
{
printf("A caught exception.\n");
}
}
int main(int argc, char* argv[])
{
A();
return 0;
}
4、输出:
5、注意编译器实现差异
vc编译器的版本差异和编译选项的差异,vc2005的/EHsc标记。
class Object
{
public:
Object(const char tag) : m_tag(tag)
{printf("Object [%c] %p\n", m_tag, this);};
~Object()
{printf("~Object [%c] %p\n", m_tag, this);};
private:
char m_tag;
};
void E(void)
{
Object e('E');
//try
{
throw("");
}
//catch (...)
{
}
}
void D(void)
{
Object d('D');
E();
}
void C(void)
{
Object c('C');
D();
}
void B(void)
{
Object b('B');
//try
{
C();
}
//catch(...)
{
printf("B caught exception.\n");
}
}
void A(void)
{
Object a('A');
try
{
B();
}catch(...)
{
printf("A caught exception.\n");
}
}
int main(int argc, char* argv[])
{
A();
return 0;
}
不带/EHsc产生的结果符合预期