2016-05-13 113 views
3

我是C新手,想通過Ruby源代碼學習更多知識。修改Ruby源代碼 - 無法修改方法

當我從源代碼編譯Ruby時,它似乎沒有認識到我對方法定義所做的任何更改。但是,如果我添加一個新方法,比如說字符串,指向一個修改過的方法,那麼新方法按預期工作。

# string.c 

static VALUE 
rb_str_empty(VALUE str) 
{ 
    return Qtrue; 
} 

... 

rb_define_method(rb_cString, "empty?", rb_str_empty, 0); 
rb_define_method(rb_cString, "my_empty?", rb_str_empty, 0); 

然後在Ruby控制檯中,我們可以看到,新方法體現了新的定義,但是老方法仍然有效,猶如方法是修改。

$ irb 
> "sdf".my_empty? 
true 
> "sdf".empty? 
false 

Ruby如何「保護」原始方法定義?我怎樣才能讓我的更改註冊?

+0

除了是一個非常深入和完全正確的解釋,@matt下面的答案說明了同一個屬性,他在那裏顯示''「 asdf「.send(:空?)#=> true'。 –

回答

4

Ruby的當前版本使用虛擬機。當你運行一些Ruby代碼時,它首先被編譯成字節碼,然後這個字節碼被虛擬機執行。虛擬機包括指令,如分配變量,創建類,定義方法和(對於這種情況重要的)調度方法。但是,對於一些常用的方法,還有一些特殊的優化字節碼指令繞過了常規的方法調度程序。 empty? is one such method

您可以用RubyVM::InstructionSequence.compile(和disasm查看)來檢查一小段Ruby代碼的字節碼。第一「正常」方法調度(與不存在的方法foo):

> puts RubyVM::InstructionSequence.compile('"asdf".foo').disasm 
== disasm: #<ISeq:<compiled>@<compiled>>================================ 
0000 trace   1            ( 1) 
0002 putstring  "asdf" 
0004 opt_send_without_block <callinfo!mid:foo, argc:0, ARGS_SIMPLE>, <callcache> 
0007 leave 

opt_send_without_block的是方法調度指令,試圖調用foomid「方法ID」)。

用優化的字節碼

現在對於empty?

> puts RubyVM::InstructionSequence.compile('"asdf".empty?').disasm 
== disasm: #<ISeq:<compiled>@<compiled>>================================ 
0000 trace   1            ( 1) 
0002 putstring  "asdf" 
0004 opt_empty_p  <callinfo!mid:empty?, argc:0, ARGS_SIMPLE>, <callcache> 
0007 leave 

opt_empty_p是用於empty?方法專門字節代碼指令。

如果你比較了與source for the normal function implementing String#empty?(更改了功能),該指令的來源,你可以看到,在case of the receiver being a String指令代碼複製功能的代碼,完全繞過該功能(在某些情況下,這些優化的指令有直接調用實現函數,繞過方法調度代碼但使用相同的實現)。

該指令確實包含一個檢查以確保該方法在Ruby中沒有被替換,但是顯然這並不包括像這裏那樣對C源程序的修改。

我想,如果你使用send,因爲這不會編譯到優化的指令,你應該得到的功能的修改版本:

'asdf'.send :empty? 

如果你設置編輯和重新編譯的Ruby,你應該也可以在文件insns.def中更改指令本身。該文件用於在構建過程中爲指令創建代碼。它不是C本身,但每個指令塊的內容都是純C的。

+0

謝謝@matt。這非常重要。在我的搜索中,我實際上偶然發現了'opt_empty_p',但並不理解它。謝謝你的深刻答案。 – steel