2016-05-13 53 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