使用RubyInline在C中修改Ruby数组

3

我在Ruby中使用数组,并希望通过扩展.normalize方法来处理它们。该方法应修改数组,使其所有元素的总和为1。但是,在Ruby中进行此操作过于昂贵,因此我希望通过RubyInline在C中完成。

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);

有几个内联宝石吗?我已经安装了RVM并执行了gem install inline。运行您的脚本:undefined method 'inline' for Array:Class (NoMethodError)。检查了这个宝石的RDoc,没有内联方法。看起来像是一个编辑器,而不是C编译器。 - BernardK
我也遇到了这个问题。你需要使用 gem install RubyInline 进行安装。 - user1939480
我也发现它在开头。两年前我做了很少的Ruby-C扩展,宏已经完全改变了(之前是RARRAY(<array>) -> ptr)。我不确定Check_Type(self, T_ARRAY);是否仍然有效。如果我把它放在块的开头==> in 'normalize': wrong argument type false (expected Array) (TypeError) - BernardK
你看到右边的 相关 栏了吗?其中包括 -> https://dev59.com/E1LTa4cB1Zd3GeqPeeVD?rq=1 - BernardK
1个回答

3

这里有几个需要修复的问题:

当您使用 c_raw 时,RubyInline 不会尝试检测参数数量,而是假设您想使用可变数量的参数。您可以覆盖此行为(传递 :arity => 0),或更改方法签名为

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

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

接下来,由于所有数组元素都小于1,所以你现在总是用零填充数组,然后将其转换为整数,因此得到0-使用rb_float_new将double转换回ruby Float

最后,你的返回值是错误的,它是一个VALUE *而不是一个VALUE。你可能想要返回self

最后,在Ruby中更符合习惯的方法名应该叫做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

太好了!下次我需要编写一些C粘合代码时,我会使用这个解决方案。请注意,为了避免编译错误(“error: expected identifier or ‘(’ before ‘int’”),我不得不用“int len, i;”替换“size_t len, int i;”。 - BernardK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接