這一陣子真沒時間,9月上旬更沒時間,頭大.
前天寫面試題目的時候遇到了setjmp和longjmp這兩個方法,
於是就想到r uby的異常處理是如何實現的,順道研究下.
其他的ruby相關的實現現在真沒時間寫.但肯定要寫,因為我喜歡r ,不是一般的喜歡.
兵馬未動,糧草先行.
我想看看raise怎麼實現的,但當我在irb中敲入了raise後,我卻不知道在gdb中該對哪個方法下斷點.
冒然出擊肯定是盲目的,就像菲律賓特警解救香港遊客人質一樣,一點思路都沒有.
那好吧,我還是要做點預習功課,翻開**去找raise對應的c方法.
通過閱讀eval.c的**,發現裡面有如下方法:
void其中raise這個字串對應的方法是: rb_f_raise,init_eval(void)
static value同時我們也可以看到其他方法的底層實現,比如:rb_f_raise(int argc, value *argv)
}rb_raise_jump(rb_make_exception(argc, argv));
return qnil; /* not reached */
}
我們既然要理解ruby異常機制,那麼我們就要對rb_f_raise進行仔細的分析.
現在我設計這樣乙個場景:
在irb中通過raise丟擲乙個異常
在gdb中對rb_f_raise設定斷點
執行 irb中的raise**
在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 `'
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)
if(a==b)
else知道了這兩個函式,我們就可以開心一下了,原來好多語言都使用它們去實現異常處理阿,只是功能豐富程度不同,但通過了解上面這些,至少我們知道了乙個大概的思路,給我們自己的語言設計啟發了不少.
話題再轉回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=) at eval.c:530
#3 0x0000000100028a07 in rb_f_raise (argc=, argv=) at eval.c:474
整個raise的流程差不多就這樣,我在對其中個別幾個函式再剖析一下:
rb_make_exception是在rb_raise_jump的時候呼叫的,
他的作用就是生成乙個異常物件給rb去raise.
現在我們來看一下這個exception的生成過程.
value羅列這麼多**的確很不好意思,因為不是每行都需要說明一下,但我又怕只取部分**片段的話,會有人看不明白.rb_make_exception(int argc, value *argv)
n = 0;
goto exception_call;
case 2:
case 3:
n = 1;
exception_call:
const_id(exception, "exception");
if (!rb_respond_to(argv[0], exception))
mesg = rb_funcall(argv[0], exception, n, argv[1]);
break;
default:
rb_raise(rb_eargerror, "wrong number of arguments");
break;
}if (argc > 0)
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型別的rb_intern_id_cache用來儲存exception的c實現,這樣再次訪問這個方法的時候就不用使用rb_intern2去取了.#define const_id(var, str) \
do const_id_cache(var =, str) while (0)
貼一下setjmp和longjmp的解釋:
setjmp longjmp 寫道
setjmp|longjmp
與刺激的abort()和exit()相比,goto語句看起來是處理異常的更可行方案。不幸的是,goto是本地的:它只能跳到所在函式內部的標號上,而不能將控制權轉移到所在程式的任意地點(當然,除非你的所有**都在main體中)。
為了解決這個限制,c函式庫提供了setjmp()和longjmp()函式,它們分別承擔非區域性標號和goto作用。標頭檔案申明了這些函式及同時所需的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()標記相應的異常處理程式。
菜鳥的奮鬥 從排序開始走進程式的世界
今天就寫個歸併排序吧,簡單的功能,實現整數排序,沒有苛刻的測試用例。表示部落格今天開張了 陸陸續續的編寫,並參考了shan9liang的文章 include iostream using namespace std define max 2147483647 void merge int vec,i...
從開源開始
把程式 全部公開是非常符合人性。這大概因為人性是懶惰的。既然能夠用電腦完成,就不要用人來完成。但電腦還是需要人來控制。於是,有眾多的人辛辛苦苦地加入了程式設計師的行列裡。開源後程式設計師也可以懶一些,把除錯 和增加功能交給了大眾。同時獲益的也有大眾,他們可以不做出重複勞動了。是的,多好啊,他們可以不...
從 0 1走進 Kaggle實戰平台
從公司的角度來講,可以提供一些資料,進而提出乙個實際需要解決的問題 從參賽者的角度來講,他們將組隊參與專案,針對其中乙個問題提出解決方案,最終由公司選出的最佳方案可以獲得5k 10k美金的獎金。kaggle的建立初衷及運營模式,即任用最聰明的人解決世界上最棘手的問題 除此之外,kaggle官方每年還...