3

I have arrays in Ruby and I would like to extend them with .normalize method. This method should modify array such that all it`s elements sum up to 1. This is way too expensive in Ruby, so I want to do it in C with 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

This results in

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

Aborted (core dumped)

EDIT: after some debugging, crash seems to come at

VALUE* array = RARRAY_PTR(self);
4
  • Are there several inline gems ? I have RVM and did gem install inline. Running your script : undefined method 'inline' for Array:Class (NoMethodError). Checked the RDoc for this gem, no inline method. Seems to be an editor, not a C compiler. Commented Dec 31, 2012 at 12:08
  • I did run into this too. You need to install it with gem install RubyInline. Commented Dec 31, 2012 at 13:56
  • I have also found that it is at the beginning. I did very few Ruby-C extension 2 years ago, the macros have completely changed (before it was RARRAY(<array>) -> ptr). I'm not sure if Check_Type(self, T_ARRAY); is still valid. If I put it at the beginning of the block ==> in 'normalize': wrong argument type false (expected Array) (TypeError) Commented Dec 31, 2012 at 17:31
  • Have you seen the column Related on the right ? Among others -> stackoverflow.com/questions/4523597/… Commented Dec 31, 2012 at 17:49

1 Answer 1

3

There are a few things to fix here:

When you use c_raw rubyinline doesn't try to detect arity, and instead assumes that you want to use a variable number of arguments. You can either override this (pass :arity => 0) or change your method signature to

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

At the moment rubyinline is assuming your method has that signature, so you're probably reinterpreting the integer 0 as a pointer.

Next up, at the moment you're always filling the arrays with zero, because all the array elements are < 1, and then you're converting to an integer so you get 0 - use rb_float_new to turn a double back into a ruby Float.

Lastly, your return value is wrong, it's a VALUE * instead of a VALUE. You probably want to return self instead.

Finally it would be more ruby-like to call your method normalize!. By default ruby inline extracts the method name from the c function name, which of course doesn't let you use exclamation marks like that. You can override that with the method_name option.

Alltogether, my version of your example looks like

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
Sign up to request clarification or add additional context in comments.

1 Comment

Great ! I'll use this solution the next time I need to write some C glue code. Note that in order to avoid a compilation error (error: expected identifier or ‘(’ before ‘int’), I had to replace size_t len, int i; by int len, i;.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.