zoukankan      html  css  js  c++  java
  • gcc如何处理函数的throw属性

    一、问题
    在c++的语法中,可以在函数声明中添加throw(),throw(type1, type2)之类的说明,前者声明该函数不被抛出任何异常,后者则是声明该函数只会抛出type1,type2类型的异常。当然这里并不是像孔乙己一样来说明回字的四种写法;更不是为这个语法摇旗呐喊,相反,各种论调都是不建议使用这中语法的。这里讨论这个问题只是觉得这个语义对于编译器来说将如何实现呢?因为这个语法是一个看起来比较别扭的功能,可以预感到编译器实现这个功能的时候应该也会比较别扭。
    下面是一个最为简单直观的例子,从这个例子上看,函数声明为只抛出double类型异常,但是事实上抛出了一个int类型的异常,这个问题在编译的时候没有任何编译错误,但是运行的时候会出现进程直接被SIGABRT退出,这个也是尽量不要使用这种语法的一个有力说明。
    tsecer@harry: cat nothrowcallthrow.cpp
    #include <stdio.h>
     
    struct harry
    {
    public:
        void tsecer() throw(double) 
        {
            throw 0;
        }
    };
     
    int main()
    {
        try 
        {
            harry().tsecer();
        } 
        catch (int &i) 
        {
            printf("catch a int %d expection ", i);
        }
        catch(...)
        {
            printf("catch default expection ");
        }
        return 0;
    }
    tsecer@harry: g++ nothrowcallthrow.cpp -o nothrowcallthrow
    tsecer@harry: ./nothrowcallthrow 
    terminate called after throwing an instance of 'int'
    Aborted (core dumped)
    tsecer@harry: 
    二、编译器如何实现这个功能
    这里的例子里是只列出了double类型,但是理论上可以允许任意多类型。尽管如此,一个函数可以抛出的异常是可以枚举的,这样就需要编译器为这个函数进行一些定制化生成,从而允许且只允许这些声明的类型被抛出。这里我们可以想象为一个黑洞,只是允许一些特定的类型从函数中逃逸;或者说看作一个国家有签证的人才可以出国。从编译器的实现来看,大致是这样一个思想,比方说对于前面的函数
    void tsecer() throw(type1, type2)
    {
    mainbody
    }
    编译器可以转换为这样的形式
    void tsecer() throw(type1, type2)
    {
    try
    {
    mainbody
    }
    catch (type1 &)
    {
    throw
    }
    catch (type2 &)
    {
    throw
    }
    catch(...)
    {
    expection handler
    }
    }
    其中的try catch就是编译器生成的对于应用不可见的代码。
    三、看下前面代码生成的汇编
    (gdb) disas harry::tsecer
    Dump of assembler code for function _ZN5harry6tsecerEv:
    0x000000000040094e <_ZN5harry6tsecerEv+0>:      push   %rbp
    0x000000000040094f <_ZN5harry6tsecerEv+1>:      mov    %rsp,%rbp
    0x0000000000400952 <_ZN5harry6tsecerEv+4>:      sub    $0x10,%rsp
    0x0000000000400956 <_ZN5harry6tsecerEv+8>:      mov    %rdi,0xfffffffffffffff8(%rbp)
    0x000000000040095a <_ZN5harry6tsecerEv+12>:     mov    $0x4,%edi
    0x000000000040095f <_ZN5harry6tsecerEv+17>:     callq  0x400778 <__cxa_allocate_exception@plt>
    0x0000000000400964 <_ZN5harry6tsecerEv+22>:     mov    %rax,%rdi
    0x0000000000400967 <_ZN5harry6tsecerEv+25>:     mov    %rdi,%rax
    0x000000000040096a <_ZN5harry6tsecerEv+28>:     movl   $0x0,(%rax)
    0x0000000000400970 <_ZN5harry6tsecerEv+34>:     mov    $0x0,%edx
    0x0000000000400975 <_ZN5harry6tsecerEv+39>:     mov    $0x500eb0,%esi
    0x000000000040097a <_ZN5harry6tsecerEv+44>:     callq  0x4007c8 <__cxa_throw@plt>
    0x000000000040097f <_ZN5harry6tsecerEv+49>:     mov    %rax,0xfffffffffffffff0(%rbp)
    0x0000000000400983 <_ZN5harry6tsecerEv+53>:     cmp    $0xffffffffffffffff,%rdx
    0x0000000000400987 <_ZN5harry6tsecerEv+57>:     je     0x400992 <_ZN5harry6tsecerEv+68>
    0x0000000000400989 <_ZN5harry6tsecerEv+59>:     mov    0xfffffffffffffff0(%rbp),%rdi
    0x000000000040098d <_ZN5harry6tsecerEv+63>:     callq  0x4007b8 <_Unwind_Resume@plt>
    0x0000000000400992 <_ZN5harry6tsecerEv+68>:     mov    0xfffffffffffffff0(%rbp),%rdi
    0x0000000000400996 <_ZN5harry6tsecerEv+72>:     callq  0x400768 <__cxa_call_unexpected@plt>
    End of assembler dump.
    (gdb) 
    反汇编最后的__cxa_call_unexpected就是未匹配类型的默认处理函数,不在声明中声明的类型将在这里被集中处理,从名字上看,进而导致进程退出。
    四、允许类型的识别
    在__cxa_throw之后,有一个判断,如果类型返回值不等于-1,则会继续展开,这个地方可以推测,对于允许逸出的类型,这个地方返回值应该是-1;对应地,不允许逃逸的类型将会返回-1,从而跳转到__cxa_call_unexpected来终止进程。
    0x0000000000400983 <_ZN5harry6tsecerEv+53>:     cmp    $0xffffffffffffffff,%rdx
    0x0000000000400987 <_ZN5harry6tsecerEv+57>:     je     0x400992 <_ZN5harry6tsecerEv+68>
    __gxx_personality_v0函数中
          while (1)
    {
      p = action_record;
      p = read_sleb128 (p, &ar_filter);
      read_sleb128 (p, &ar_disp);
     
      if (ar_filter == 0)
        {
    ……
        }
      else if (ar_filter > 0)
        {
    ……
        }
      else
        {
          // Negative filter values are exception specifications.
          // ??? How do foreign exceptions fit in?  As far as I can
          // see we can't match because there's no __cxa_exception
          // object to stuff bits in for __cxa_call_unexpected to use.
          // Allow them iff the exception spec is non-empty.  I.e.
          // a throw() specification results in __unexpected.
          if (throw_type
      ? ! check_exception_spec (&info, throw_type, thrown_ptr,
        ar_filter)
      : empty_exception_spec (&info, ar_filter))
    {
      saw_handler = true;
      break;
    }
        }
     
      if (ar_disp == 0)
        break;
      action_record = p + ar_disp;
    }
          if (saw_handler)
    {
      handler_switch_value = ar_filter;
      found_type = found_handler;
    }
    ……
     
      /* For targets with pointers smaller than the word size, we must extend the
         pointer, and this extension is target dependent.  */
      _Unwind_SetGR (context, __builtin_eh_return_data_regno (0),
     __builtin_extend_pointer (ue_header));
      _Unwind_SetGR (context, __builtin_eh_return_data_regno (1),
     handler_switch_value);
      _Unwind_SetIP (context, landing_pad);
    #ifdef __ARM_EABI_UNWINDER__
      if (found_type == found_cleanup)
        __cxa_begin_cleanup(ue_header);
    #endif
      return _URC_INSTALL_CONTEXT;
    }
    可见其中的handler_switch_value最终还是从生成的代码中的ar_filter中读取,该值的读取通过 read_sleb128 (p, &ar_filter);函数完成。前面的代码对于负数的判断也是如果匹配则继续搜索,不在类型列表则返回负数(-1),这也是throw 1走到的流程。
  • 相关阅读:
    c# 扩展方法奇思妙用基础篇五:Dictionary<TKey, TValue> 扩展
    c# 扩展方法奇思妙用基础篇九:Expression 扩展
    c# 扩展方法奇思妙用高级篇一:改进 Scottgu 的 "In" 扩展
    c# 扩展方法奇思妙用高级篇三:Enumerable.Cast<T> 应用
    c# 扩展方法奇思妙用高级篇二:Aggregate 扩展及其改进
    c# 扩展方法奇思妙用基础篇四:Random 扩展
    c# 扩展方法奇思妙用基础篇七:IsBetween 通用扩展
    c# 扩展方法奇思妙用基础篇三:byte 常用扩展
    c# 扩展方法奇思妙用基础篇八:Distinct 扩展
    c# 扩展方法奇思妙用基础篇 六:WhereIf 扩展
  • 原文地址:https://www.cnblogs.com/tsecer/p/10487738.html
Copyright © 2011-2022 走看看