3

我在 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);
4

1 回答 1

3

这里有几件事需要解决:

当您使用c_rawruby​​inline 时,不会尝试检测 arity,而是假设您要使用可变数量的参数。您可以覆盖它(通过:arity => 0)或将您的方法签名更改为

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

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

接下来,此时您总是用零填充数组,因为所有数组元素都 < 1,然后您将转换为整数,因此您得到 0 - 用于rb_float_new将 double 转换回 rubyFloat​​ 。

最后,您的返回值是错误的,它是 aVALUE *而不是 a 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
于 2012-12-31T18:08:32.337 回答