2

My ruby based data analysis project needs high performance. I plan to create an inline C function that reads a piece of data and updates some global variables, which I can do more processing in ruby.

The following toy code:

require 'inline'
class Foo
  inline :C  do |builder|
    builder.c_raw_singleton <<SRC
     static char *cstr = "hi you'all, welcome";
      void write_global(VALUE self, VALUE *name){
        rb_gv_set(rb_string_value_cstr(name), rb_str_new_cstr(cstr));
        cstr[1] ++;
      }
SRC
  end
end


$bar = ""
Foo.write_global('bar') #=> 1
puts $bar;
Foo.write_global('bar') #=> 1
puts $bar;

will cause exception on ruby interpreter.

I wonder what the problem is.

UPDATE 1

Thanks to @mu-is-too-short who pointed out the constant string can't be modified, I changed it to be a buffer (see the following), but got compile errors like

WARNING: '&cstr[0]' not understood
WARNING: '"%s"' not understood
WARNING: '"hi you'all' not understood
WARNING: 'welcome"' not understood
WARNING: Can't find signature in "\t static char cstr[20];static int f(&cstr[0], \"%s\", \"hi you'all, welcome\");\n      void write_global(VALUE self, VALUE *name){\n        rb_gv_set(rb_string_value_cstr(name), rb_str_new_cstr(cstr));\n\t\tcstr[1] ++;\n      }\n"
inline1a.rb:5:37: error: expected declaration specifiers or ‘...’ before ‘&’ token
     builder.c_raw_singleton <<SRC

Here is the new code that doesn't try to modify constant string.

require 'inline'
class Foo
  inline :C  do |builder|
    builder.c_raw_singleton <<SRC
     static char cstr[20];
     sprintf(&cstr[0], "%s", "hi you'all, welcome");
      void write_global(VALUE self, VALUE *name){
        rb_gv_set(rb_string_value_cstr(name), rb_str_new_cstr(cstr));
        cstr[1] ++;
      }
SRC
  end
end


$bar = ""
Foo.write_global('bar') #=> 1
puts $bar;
Foo.write_global('bar') #=> 1
puts $bar;
packetie
  • 4,839
  • 8
  • 37
  • 72
  • 1
    Ruby and C are two very different languages. If you're not familiar with how to use C effectively you'll need to at least understand the fundamentals. The good news is C is pretty simple as far as languages go, with a terse, minimal sort of syntax. The difficulty comes in applying concepts like pointers and pointer math effectively. – tadman Oct 18 '17 at 05:26
  • I am much more familiar with C (sorry for the obvious oversight of modifying a constant string). The problem is, I need to know how to modify a global ruby variable but don't know how. Thx – packetie Oct 20 '17 at 04:30
  • Don't let Ruby's casual style confuse your C code then. A call to `sprintf` needs to be inside a function, but it's also baffling that you're using `sprintf` at all instead of something simple like `strncpy` or in this case, given how you've failed to initialize `cstr`, `memcpy`. – tadman Oct 20 '17 at 16:37

2 Answers2

2

Your problem is that you're trying to modify read-only memory. If you get all the clutter out of the way, you can show this with a small C program:

int
main(int argc, char **argv) {
  char *cstr = "hi you'all, welcome";
  cstr[1]++;
  return 0;
}

If you compile that and run it you should get a bus error (or similar) because cstr is in read-only memory and cstr[1]++ is trying to modify that read-only memory. There is some discussion on this over in Is it possible to modify a string of char in C? and probably hundreds of other questions in the tag.

The solution is to not modify cstr. I don't have enough details to offer more concrete advice.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
1

Watch out for how RubyInline defines your Ruby methods. If you use c_raw or c_raw_singleton then it uses -1 as the arity in the call to rb_define_method or rb_define_singleton_method, so the method is called as:

VALUE func(int argc, VALUE *argv, VALUE obj)

So in your case your signature should be something like:

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

Here’s a version of your class with that change. I’ve also moved the cstr declaration into the prefix to avoid a warning and used StringValueCStr instead of rb_string_value_cstr as it is better documented, but I’ve omitted any error checking (which you should add as otherwise you could get further segfaults):

class Foo
  inline :C  do |builder|

    builder.prefix("static char cstr[] = \"hi you'all, welcome\";")

    builder.c_raw_singleton <<SRC
      VALUE write_global(int argc, VALUE* argv, VALUE self){
        rb_gv_set(StringValueCStr(argv[0]), rb_str_new_cstr(cstr));
        cstr[1] ++;
        return Qnil;
      }
SRC
  end
end

With this class, the output of your code is:

hi you'all, welcome
hj you'all, welcome
matt
  • 78,533
  • 8
  • 163
  • 197