從main c開始走進Ruby 異常

2021-08-25 09:33:34 字數 3918 閱讀 3244

這一陣子真沒時間,9月上旬更沒時間,頭大.

前天寫面試題目的時候遇到了setjmp和longjmp這兩個方法,

於是就想到r uby的異常處理是如何實現的,順道研究下.

其他的ruby相關的實現現在真沒時間寫.但肯定要寫,因為我喜歡r ,不是一般的喜歡.

兵馬未動,糧草先行.

我想看看raise怎麼實現的,但當我在irb中敲入了raise後,我卻不知道在gdb中該對哪個方法下斷點.

冒然出擊肯定是盲目的,就像菲律賓特警解救香港遊客人質一樣,一點思路都沒有.

那好吧,我還是要做點預習功課,翻開**去找raise對應的c方法.

通過閱讀eval.c的**,發現裡面有如下方法:

void

init_eval(void)

其中raise這個字串對應的方法是: rb_f_raise,

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)			\

#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作用。標頭檔案申明了這些函式及同時所需的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官方每年還...