• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

从main.c开始走进Ruby-异常

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

这一阵子真没时间,9月上旬更没时间,头大.

前天写面试题目的时候遇到了setjmp和longjmp这两个方法,

于是就想到R uby的异常处理是如何实现的,顺道研究下.

其他的Ruby相关的实现现在真没时间写.但肯定要写,因为我喜欢R ,不是一般的喜欢.

 

===============================

 

兵马未动,粮草先行.

 

我想看看raise怎么实现的,但当我在irb中敲入了raise后,我却不知道在gdb中该对哪个方法下断点.

冒然出击肯定是盲目的,就像菲律宾特警解救香港游客人质一样,一点思路都没有.

那好吧,我还是要做点预习功课,翻开代码去找raise对应的C方法.

 

 

通过阅读eval.c的代码,发现里面有如下方法:

 

 

C代码
 
void  
Init_eval(void)  
{  
    rb_define_virtual_variable("$@", errat_getter, errat_setter);  
    rb_define_virtual_variable("$!", errinfo_getter, 0);  
  
    rb_define_global_function("raise", rb_f_raise, -1);  
    rb_define_global_function("fail", rb_f_raise, -1);  
/* 
    .......... 
*/  
}  
 

其中raise这个字符串对应的方法是: rb_f_raise,

 

 

C代码
 
static VALUE  
rb_f_raise(int argc, VALUE *argv)  
{  
    VALUE err;  
    if (argc == 0) {  
    err = get_errinfo();  
    if (!NIL_P(err)) {  
        argc = 1;  
        argv = &err;  
    }  
    }  
    rb_raise_jump(rb_make_exception(argc, argv));  
    return Qnil;        /* not reached */  
}  
 

同时我们也可以看到其他方法的底层实现,比如:

 

  • $@是获得出错信息的所在代码行,通过errat_getter实现
  • $!是获得出错信息,通过errinfo_getter实现

我们既然要理解Ruby异常机制,那么我们就要对rb_f_raise进行仔细的分析.

现在我设计这样一个场景:

 

  1. 在irb中通过raise抛出一个异常
  2. 在gdb中对rb_f_raise设置断点
  3. 执行 irb中的raise代码
  4. 在gdb中step进入rb_f_raise及其里面,通过bt观察它的调用栈以及最底层的实现方式.

 

看我如下操作:

 

irb 写道
>> raise ArgumentError,"Debug Ruby from main.c"
ArgumentError: Debug Ruby from main.c
from (irb):15
from /usr/local/ruby-1.9.1/bin/irb:12:in `<main>'

 

gdb 写道
(gdb) b rb_f_raise
Breakpoint 10 at 0x1000289dc: file eval.c, line 467.
(gdb) c
Continuing.
 

 

irb 写道
>> raise ArgumentError,"Debug Ruby from main.c"

 

 

gdb 写道
Breakpoint 10, rb_f_raise (argc=2, argv=0x100400298) at eval.c:467
467 if (argc == 0) {
 

这个时候我们已经断到了rb_f_raise,下面就是要step进入其中,看看一个语言如何设计对异常的处理.

 

我没有把每一步的代码都贴出来,而是走到了最里面,当我遇到以下两个函数的时候,我认为我们就可以大概知道Ruby的异常处理是怎么实现的了.

 

 

C代码
 
int _setjmp(jmp_buf);  
void    _longjmp(jmp_buf, int);  
     

    深入过C++和Java实现的同学肯定也见过这两个函数,他们的作用就是:

     

    • setjmp:收集当前堆栈信息并且保存到jmp_buf中,返回值是longjmp,如果不是从longjmp返回的,则返回0.
    • longjmp:跳转到jmp_buf设定的位置,并将int返回给setjmp.

    下面还有setjmp和longjmp的解释.

     

    我出过一道面试题目,就是关于用setjmp.h中的方法实现异常处理的,在这里可以当作一个简单应用的例子,如下:

     

     

    C代码
    代码

    #include
    <setjmp.h>
    jmp_buf jb;
    int ret = setjmp(jb);
    switch(ret)
    {
    case 0:/*ok*/;break;
    case 1:exc1_handler();break;
    case 2:exc2_handler();break;
    default:default_handler();
    }

    if(a==b){/*do ok*/}
    else{/*raise exception*/longjmp(jb,1)}

     

       

      知道了这两个函数,我们就可以开心一下了,原来好多语言都使用它们去实现异常处理阿,只是功能丰富程度不同,但通过了解上面这些,至少我们知道了一个大概的思路,给我们自己的语言设计启发了不少.

       

      话题再转回R uby的调试中,在断到longjmp的时候,我使用bt命令查看了一下调用栈,可以比较清楚的看到当在Ruby层面执行raise语句的时候,它是通过调用哪些关键函数来实现异常处理功能的.

       

      gdb 写道
      #0 rb_sourcefile () at vm.c:754
      #1 0x00000001000274cd in rb_longjmp (tag=6, mesg=4304442240) at eval.c:358
      #2 0x00000001000277e8 in rb_raise_jump (mesg=<value temporarily unavailable, due to optimizations>) at eval.c:530
      #3 0x0000000100028a07 in rb_f_raise (argc=<value temporarily unavailable, due to optimizations>, argv=<value temporarily unavailable, due to optimizations>) at eval.c:474
       

       

       

      整个raise的流程差不多就这样,我在对其中个别几个函数再剖析一下:

       

      rb_make_exception是在rb_raise_jump的时候调用的,

      他的作用就是生成一个异常对象给rb去raise.

      现在我们来看一下这个exception的生成过程.

       

       

      C代码
       
      VALUE  
      rb_make_exception(int argc, VALUE *argv)  
      {  
          VALUE mesg;  
          ID exception;  
          int n;  
        
          mesg = Qnil;  
          switch (argc) {  
            case 0:  
          break;  
            case 1:  
          if (NIL_P(argv[0]))  
              break;  
          mesg = rb_check_string_type(argv[0]);  
          if (!NIL_P(mesg)) {  
              mesg = rb_exc_new3(rb_eRuntimeError, mesg);  
              break;  
          }  
          n = 0;  
          goto exception_call;  
        
            case 2:  
            case 3:  
          n = 1;  
            exception_call:  
          CONST_ID(exception, "exception");  
          if (!rb_respond_to(argv[0], exception)) {  
              rb_raise(rb_eTypeError, "exception class/object expected");  
          }  
          mesg = rb_funcall(argv[0], exception, n, argv[1]);  
          break;  
            default:  
          rb_raise(rb_eArgError, "wrong number of arguments");  
          break;  
          }  
          if (argc > 0) {  
          if (!rb_obj_is_kind_of(mesg, rb_eException))  
              rb_raise(rb_eTypeError, "exception object expected");  
          if (argc > 2)  
              set_backtrace(mesg, argv[2]);  
          }  
        
          return mesg;  
      }  
      

         

        罗列这么多代码的确很不好意思,因为不是每行都需要说明一下,但我又怕只取部分代码片段的话,会有人看不明白.

         

         

        mesg = rb_check_string_type(argv[0]);

        这里是将错误信息argv转换为String

         

         

        mesg = rb_exc_new3(rb_eRuntimeError, mesg);

        这里将mesg变成一个异常的对象,而在此之前它还只是个String.

        我们看到更改mesg Ruby类型的时候都不需要加强制转换,就是因为这是在C层面,一切R uby对象都是VALUE.

         

        rb_exc_new3是通过rb_funcall(etype, rb_intern("new"), 1, str);来实现的.

        我们又看到了rb_intern方法,这个方法是把ruby的方法名字转换为id,然后找到该id对应的该函数的C的实现,再执行C的实现.这是C中使用Ruby函数的一个通用思路.

         

         

        CONST_ID(exception, "exception");

        这个宏定义如下:

         

         

        C代码
         
        #define CONST_ID_CACHE(result, str)         \  
            {                           \  
            static ID rb_intern_id_cache;           \  
            if (!rb_intern_id_cache)            \  
                rb_intern_id_cache = rb_intern2(str, strlen(str));  \  
            result rb_intern_id_cache;          \  
            }  
        #define CONST_ID(var, str) \  
            do CONST_ID_CACHE(var =, str) while (0)  
        

           其中static类型的rb_intern_id_cache用来保存exception的C实现,这样再次访问这个方法的时候就不用使用rb_intern2去取了.

           

           

          贴一下setjmp和longjmp的解释:

           

           

          setjmp longjmp 写道
          setjmp|longjmp

           

            与刺激的abort()和exit()相比,goto语句看起来是处理异常的更可行方案。不幸的是,goto是本地的:它只能跳到所在函数内部的标号上,而不能将控制权转移到所在程序的任意地点(当然,除非你的所有代码都在main体中)。
            为了解决这个限制,C函数库提供了setjmp()和longjmp()函数,它们分别承担非局部标号和goto作用。头文件<setjmp.h>申明了这些函数及同时所需的jmp_buf数据类型。
            原理非常简单:
            1.setjmp(j)设置“jump”点,用正确的程序上下文填充jmp_buf对象j。这个上下文包括程序存放位置、栈和框架指针,其它重要的寄存器和内存数据。当初始化完jump的上下文,setjmp()返回0值。
            2. 以后调用longjmp(j,r)的效果就是一个非局部的goto或“长跳转”到由j描述的上下文处(也就是到那原来设置j的setjmp()处)。当作为长跳转的目标而被调用时,setjmp()返回r或1(如果r设为0的话)。(记住,setjmp()不能在这种情况时返回0。)
            通过有两类返回值,setjmp()让你知道它正在被怎么使用。当设置j时,setjmp()如你期望地执行;但当作为长跳转的目标时,setjmp()就从外面“唤醒”它的上下文。你可以用longjmp()来终止异常,用setjmp()标记相应的异常处理程序。

           


          鲜花

          握手

          雷人

          路过

          鸡蛋
          该文章已有0人参与评论

          请发表评论

          全部评论

          专题导读
          上一篇:
          ruby中::究竟代表什么?发布时间:2022-07-14
          下一篇:
          【转】如何安装Ruby和RubyGems发布时间:2022-07-14
          热门推荐
          阅读排行榜

          扫描微信二维码

          查看手机版网站

          随时了解更新最新资讯

          139-2527-9053

          在线客服(服务时间 9:00~18:00)

          在线QQ客服
          地址:深圳市南山区西丽大学城创智工业园
          电邮:jeky_zhao#qq.com
          移动电话:139-2527-9053

          Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap