使用RubyInline修改C中的Ruby数组

时间:2012-12-31 11:17:50

标签: c ruby

我在Ruby中有数组,我想用.normalize方法扩展它们。这个方法应该修改数组,使得它的所有元素总和为1.这在Ruby中太贵了,所以我想用C和RubyInline来做。

require "rubygems"
require "inline"

class Array
inline do |builder|
    builder.c_raw '
     static VALUE normalize(VALUE self) {
        double total_size = 0, len;
        int i;

        VALUE* array = RARRAY_PTR(self);
        len = RARRAY_LEN(self);

        for(i=0; i < len; i++){
            total_size += NUM2DBL(array[i]);
        }

        for(i=0; i < len; i++){
            array[i] = INT2NUM(NUM2DBL(array[i])/total_size);
        }

        return array;
    }'
  end
end

a = [1,2,0,0,0,0,0,3,0,4]

puts a.normalize.inspect

这导致

$ ruby tmp.rb 
tmp.rb:29: [BUG] Segmentation fault
ruby 1.8.7 (2011-06-30 patchlevel 352) [x86_64-linux]

Aborted (core dumped)

编辑:经过一些调试后,崩溃似乎来了

VALUE* array = RARRAY_PTR(self);

1 个答案:

答案 0 :(得分:3)

这里有一些事情需要解决:

使用c_raw时,rubyinline不会尝试检测arity,而是假设您要使用可变数量的参数。您可以覆盖此(pass:arity =&gt; 0)或将方法签名更改为

VALUE normalize(int argc, VALUE *argv, VALUE self)

目前rubyinline假设您的方法具有该签名,因此您可能将整数0重新解释为指针。

接下来,你总是用零填充数组,因为所有的数组元素都是&lt; 1,然后你转换为一个整数,所以你得到0 - 使用rb_float_new将双重转回红宝石Float

最后,您的返回值是错误的,它是VALUE *而不是VALUE。您可能希望返回self

最后,调用方法normalize!会更像红宝石。默认情况下,ruby inline从c函数名称中提取方法名称,这当然不允许您使用这样的感叹号。您可以使用method_name选项覆盖它。

总而言之,我的示例版本看起来像

require "rubygems"
require "inline"

class Array
  inline do |builder|
    builder.c_raw <<-'SRC', :method_name => 'normalize!', :arity => 0
     static VALUE normalize(VALUE self) {
        double total_size = 0;
        size_t len, i;

        VALUE* array = RARRAY_PTR(self);
        len = RARRAY_LEN(self);

        for(i=0; i < len; i++){
            total_size += NUM2DBL(array[i]);
        }
        for(i=0; i < len; i++){
            array[i] = rb_float_new((NUM2DBL(array[i])/total_size));
        }

        return self;
    }
    SRC
  end
end

a = [1,2,0,0,0,0,0,1,0,4]

puts a.normalize!.inspect