C++异常的幕后14:多个着陆垫与大师的教导

原文地址:https://monoinfinito.wordpress.com/2013/04/23/c-exceptions-under-the-hood-14-multiple-landing-pads-the-teachings-of-the-guru/

作者:nicolasbrailo

在大量困难的工作后,上次我们最终得到了可工作的personality函数,它无需libstdc++就能处理异常。它将不加区分地处理所有异常,但它能工作。我们尚有一个大问题还没回答:如果我们回到LSDA(语言特定数据区),我们将看到像这样的东西:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

.local_lsda_call_site_table:

    .uleb128 .LEHB0-.LFB1

    .uleb128 .LEHE0-.LEHB0

    .uleb128 .L8-.LFB1

    .uleb128 0x1

 

    .uleb128 .LEHB1-.LFB1

    .uleb128 .LEHE1-.LEHB1

    .uleb128 0

    .uleb128 0

 

    .uleb128 .LEHB2-.LFB1

    .uleb128 .LEHE2-.LEHB2

    .uleb128 .L9-.LFB1

    .uleb128 0

.local_lsda_call_site_table_end:

这里定义了3个着陆垫,即使我们仅写了一条try/catch语句。这里发生了什么?

如果你仔细阅读这个话题的上一篇文章,你可能注意到我向struct LSDA_CS的定义增加了一些注释:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

struct LSDA_CS {

    // Note start, len and lp would be void*'s, but they are actually relative

    // addresses: start and lp are relative to the start of the function, len

    // is relative to start

  

    // Offset into function from which we could handle a throw

    uint8_t start;

    // Length of the block that might throw

    uint8_t len;

    // Landing pad

    uint8_t lp;

    // Offset into action table + 1 (0 means no action)

    // Used to run destructors

    uint8_t action;

};

备注:你可以从我的github repo下载完整的源代码。

这里发生了一些有趣的事情,但让我们首先通过下面的例子按域分析这个结构体:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

void foo() {

    L0:

        try {

            do_something();

    L1:

        } catch (const Exception1& ex) {

            ...

        } catch (const Exception2& ex) {

            ...

        } catch (const ExceptionN& ex) {

            ...

        } catch (...) {

        }

    L2:

}

  • lp:从函数开头到着陆垫开头的偏移。下面例子的lp的值将是L1 – addr_of(foo)
  • action:到action表的偏移。这用于在回滚栈的同时执行清理活动。我们还没研究到这一点,目前可以忽略它。
  • start:从函数开头到try块开始的偏移:在这个例子里,这是L0 – addr_of(foo)
  • lentry块的长度。在这个例子里这将是L1 – L0

现在感兴趣的域是start与len:在有多个try/catch块的函数里,通过检查当前帧的指令指针是否在start与start + len之间,我们可以知道是否应该处理一个异常。

这解决了带有多个try/catch块的函数如何可以处理异常的谜案,但我们仍然有另一个问题:为什么在我们仅声明一个着陆垫时,有三个调用点?另外三个是可能抛出异常的地方,因此它们被添加为清理活动或着陆垫可能的地方。如果我们从GOTW学到了什么,那就是异常会在我们最意想不到的地方抛出。在调用表中对我们的抛出有一项,因为它是可能抛出的块;编译器也检测到了另外三个。

现在我们知道了start与field域作何用处,让我们修改personality函数,使得正确的着陆垫可以处理抛出的异常。前进吧。我的实现在下一篇文章里。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值