修改Ruby源 - 无法修改方法

时间:2016-05-13 20:09:50

标签: c ruby

我是C的新手,试图通过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如何“保护”原始方法定义?我怎样才能让我的更改注册?

1 个答案:

答案 0 :(得分:4)

当前版本的Ruby使用虚拟机。当你运行一些Ruby代码时,它首先被编译成字节码,然后这个字节码由虚拟机执行。 VM包括指令,用于分配变量,创建类,定义方法和(导入这种情况)调度方法。但是,对于某些常用方法,还有一些特殊优化的字节码指令可绕过正常的方法调度过程。 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_pempty?方法的专用字节码指令。

如果将此指令的源与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.