锁定老帖子 主题:从main.c开始走进Ruby-异常
该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2010-08-26
最后修改:2010-08-27
这一阵子真没时间,9月上旬更没时间,头大. 前天写面试题目的时候遇到了setjmp和longjmp这两个方法, 于是就想到R uby的异常处理是如何实现的,顺道研究下. 其他的Ruby相关的实现现在真没时间写.但肯定要写,因为我喜欢R ,不是一般的喜欢.
===============================
兵马未动,粮草先行.
我想看看raise怎么实现的,但当我在irb中敲入了raise后,我却不知道在gdb中该对哪个方法下断点. 冒然出击肯定是盲目的,就像菲律宾特警解救香港游客人质一样,一点思路都没有. 那好吧,我还是要做点预习功课,翻开代码去找raise对应的C方法.
通过阅读eval.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,
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 */ } 同时我们也可以看到其他方法的底层实现,比如:
我们既然要理解Ruby异常机制,那么我们就要对rb_f_raise进行仔细的分析. 现在我设计这样一个场景:
看我如下操作:
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的异常处理是怎么实现的了.
int _setjmp(jmp_buf); void _longjmp(jmp_buf, int); 深入过C++和Java实现的同学肯定也见过这两个函数,他们的作用就是:
下面还有setjmp和longjmp的解释.
我出过一道面试题目,就是关于用setjmp.h中的方法实现异常处理的,在这里可以当作一个简单应用的例子,如下:
#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的生成过程.
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"); 这个宏定义如下:
#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()标记相应的异常处理程序。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-08-26
我对这种泛泛而谈的文章没有什么感觉,没有实际应用做背景的东西,太乏味
|
|
返回顶楼 | |
发表时间:2010-08-26
下一站,火星 写道 我对这种泛泛而谈的文章没有什么感觉,没有实际应用做背景的东西,太乏味 请教下,什么是实际应用呢? 我写这个文章是分享我对Ruby底层的学习,没有针对个人口味.这篇文章的确没啥用途,不能告诉我们哪些新技术,新发明,但至少让我知道了一个语言层面的try\catch在底层是怎么实现的.我很有收获感.如果这是你需要的,那就对了,如果不是,那的确不合你的口味:(抱歉啊! |
|
返回顶楼 | |
发表时间:2010-08-27
这也能投隐藏贴,你们都是什么心态?
3、缝纫机 4、马耳他十字机芯——用于控制时钟的秒针运动 5、汽车变档机制 6、汽车等速万向节 7、舰炮弹--药装填系统 8、转子发动机——内燃机的一种,把热能转为旋转运动而非活塞运动
1、直列式发动机——它的汽缸肩并肩地排成一排 2、V 型发动机 ——汽缸排列在成一定角度的两个平面上 3、水平对置式发动机 ——汽缸排列在发动机相对的两个平面上
|
|
返回顶楼 | |
发表时间:2010-08-29
喜欢R,还不是一般的喜欢?
我也喜欢R,还喜欢Ruby。。 公司要人么? |
|
返回顶楼 | |
发表时间:2010-08-29
googya 写道 喜欢R,还不是一般的喜欢?
我也喜欢R,还喜欢Ruby。。 公司要人么? 我们公司不用R 我朋友公司用R,你可以把简历给我,我帮你投递.站内短信联系好了. |
|
返回顶楼 | |
发表时间:2010-08-30
对于这种实际上或许没有什么用。但是是我想做的东西的好文。我就投精华!
|
|
返回顶楼 | |
发表时间:2010-08-30
jinleileiking 写道 对于这种实际上或许没有什么用。但是是我想做的东西的好文。我就投精华!
谢谢,其实火星的要求也没错,但我的确是没什么实战功底,我不是开发,不常写代码,只是为了兴趣而学习,所以我写的东西都很少有关实战的,因为我本身就没什么实战的经历。 如果有相关实战经历的大牛能多写几篇关于Ruby调试的文章那就可以互相补充了,但千万不要投隐藏贴啊,难道一个论坛篇篇都能精华么?多虚荣,多假啊。一个繁荣文明的肯定是包容的,只有精英的世界是不现实的。 不过谢谢大家的理解和宽容,3x! |
|
返回顶楼 | |
发表时间:2010-09-07
有些就是只关心术而不关心道
|
|
返回顶楼 | |
浏览 4727 次