原文地址:https://monoinfinito.wordpress.com/2013/02/26/c-exceptions-under-the-hood-4-catching-what-you-throw/
作者:nicolasbrailo
在这个关于异常处理的系列里,通过检查编译器与链接器错误,我们已经发现不少异常抛出的秘密,但目前为止我们还看到任何关于异常处理的东西。让我们总结一下学到的异常抛出:
- 一个throw语句将被编译器翻译为两个调用,__cxa_allocate_exception与__cxa_throw。
- __cxa_allocate_exception与__cxa_throw“活”在libstdc++中。
- __cxa_allocate_exception将为新异常分配内存。
- __cxa_throw将准备一大堆东西并把这个异常转发给_Unwind_,一组libstdc++里的函数,并执行真正的栈回滚(这个ABI定义了这些函数的接口)。
目前为止相当简单,但异常捕捉有点复杂,特别因为它要求某种程度的反射(即,程序分析自己源代码的能力)。让我们继续在旧代码上尝试,在我们代码添加一些catch语句,编译它并看发生了什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #include "throw.h" #include <stdio.h> // Notice we're adding a second exception type struct Fake_Exception {}; void raise() { throw Exception(); } // We will analyze what happens if a try block doesn't catch an exception void try_but_dont_catch() { try { raise(); } catch(Fake_Exception&) { printf("Running try_but_dont_catch::catch(Fake_Exception)\n"); } printf("try_but_dont_catch handled an exception and resumed execution"); } // And also what happens when it does void catchit() { try { try_but_dont_catch(); } catch(Exception&) { printf("Running try_but_dont_catch::catch(Exception)\n"); } catch(Fake_Exception&) { printf("Running try_but_dont_catch::catch(Fake_Exception)\n"); } printf("catchit handled an exception and resumed execution"); } extern "C" { void seppuku() { catchit(); } } |
备注:你可以从我的github repo下载完整的源代码。
就像之前那样,我们的seppuku函数把C世界与C++世界链接起来,只是这次我们添加了另外一些函数调用来使得我们的栈更有趣,加上我们已经添加了一组try/catch块,因此我们可用分析libstdc++如何处理它们。
像以前那样,我们得到了一些关于缺失ABI函数的链接器错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 | > g++ -c -o throw.o -O0 -ggdb throw.cpp > gcc main.o throw.o mycppabi.o -O0 -ggdb -o app throw.o: In function `try_but_dont_catch()': throw.cpp:12: undefined reference to `__cxa_begin_catch' throw.cpp:12: undefined reference to `__cxa_end_catch' throw.o: In function `catchit()': throw.cpp:20: undefined reference to `__cxa_begin_catch' throw.cpp:20: undefined reference to `__cxa_end_catch' throw.o:(.eh_frame+0x47): undefined reference to `__gxx_personality_v0' collect2: ld returned 1 exit status |
同样这里我们看到了许多有趣的东西。对__cxa_begin_catch与__cxa_end_catch的调用可能是我们期望的东西:我们尚不了解它们,但我们可以假定它们等同于throw/__cxa_allocate/throw转换(记得我们的throw关键字被翻译为一对__cxa_allocate_exception与__cxa_throw函数,对吧?)。不过__gxx_personality_v0是新玩意,它是后面几章的中心。
Personality函数做什么用呢?在介绍这个系列时,我们谈论过它,但我们将进一步探究它,连同我们的两个新朋友,__cxa_begin_catch与__cxa_end_catch。