用递归来演示C++的stack unwinding

2015-12-19

之前某天吃晚饭的时间,想到了栈展开(stack unwinding),也就是throw出来的exception会沿着调用链反向寻找异常处理语句的过程,因为一般函数的调用链实现为栈帧的增长,所以这个过程形象的描述就是栈回溯,以前看书上写的云里雾里,觉得不够直观。想到递归函数可以直观的表达栈的递增过程,同时反向的查找也很好理解,于是写了一段代码来证明:

#include <iostream>
#include <cstdlib>

using namespace std;

static int i = 0;
void foo()
{
    int j = i++;
    if ( j == 5)
    {
        try
        {
            foo();
        }
        catch (exception &e)
        {
            cout << "catch exception " << e.what() << " in function " << __FUNCTION__ <<  " with level " << j << endl;
            exit(-1);
        }
    }
    else if (j == 10)
    {
        cout << "throw exception in function " << __FUNCTION__ <<  " with level " << j << endl;
        throw exception();
    }
    else
    {
        foo();
    }
}

int main(int argc, char **argv)
{
        foo();
        return 0;
}

可以从代码中看到,我们在递归深度10的时候throw了一个exception,并且在递归深度5的时候设置了捕捉的代码,程序的输出也证明了这一点:

$ ./a.out

throw exception in function foo with level 10

catch exception std::exception in function foo with level 5

用递归来做demo还是比较简单的,毕竟只有这一个函数,栈的增长也是其递归调用的结果,可以很方便的追踪栈轨迹,也就很容易演示栈展开这一特性了,至于如何用setjmp和longjmp来模拟try...catch这个特性,大概就是在每个函数调用栈中都调用一遍setjmp,另外注册一个回调函数指针来指示模拟异常处理语句块,每当出现问题(模拟throw),先查找本栈帧内的异常处理函数指针是否为空,若是则用函数指针指向的函数处理,若否,则longjmp到调用函数的jmp_buf处,恢复其调用现场,执行类似操作,一层层往上直到main函数。

大概如此,希望没有猜错。