Ruby arguments into C function are warped

D

Daniel Waite

I want to code a few methods in C for speed reasons. The Pick Axe book
(2nd edition) is a wonderful guide to writing Ruby code in C, but that's
not what I want. The benchmarks I ran for the Ruby-in-Ruby and Ruby-in-C
programs showed similar results. So allow me to clarify:

I do not want a Ruby array in C. I want a C array in C.

Which I have done...

int array[1000000];

static VALUE t_add(VALUE self, VALUE obj, VALUE i)
{
array = obj;
return 1;
}

static VALUE t_include(VALUE self, VALUE obj)
{
int size = sizeof(array) / sizeof(int);
int index;

for(index = 0; index < size; index++) {
if(FIX2INT(obj) == FIX2INT(array[index])) {
return Qtrue;
}
}

return Qfalse;
}

static VALUE t_at(VALUE self, VALUE obj)
{
return array[obj];
}

The value of "obj" comes from within Ruby (IRB, actually). But unless I
coerce it using FIX2INT, it's always a weird value. Say I put in 1,
it'll tell me it received 31478 or something similar.

Another thing... I thought that if I passed in an 'a' or any other
non-integer value into the add method, I'd get an error. I don't. In
fact...

require 'my_test'
mt = MyTest.new
mt.add('a', 0)
mt.at(0) # 'a'

But...

mt.include?('a') # false

So... what's happening to my arguments as they pass through Ruby and
into C?
 
T

Tim Hunter

Daniel said:
I want to code a few methods in C for speed reasons. The Pick Axe book
(2nd edition) is a wonderful guide to writing Ruby code in C, but that's
not what I want. The benchmarks I ran for the Ruby-in-Ruby and Ruby-in-C
programs showed similar results. So allow me to clarify:

I do not want a Ruby array in C. I want a C array in C.

Which I have done...

int array[1000000];

static VALUE t_add(VALUE self, VALUE obj, VALUE i)
{
array = obj;
return 1;
}

static VALUE t_include(VALUE self, VALUE obj)
{
int size = sizeof(array) / sizeof(int);
int index;

for(index = 0; index < size; index++) {
if(FIX2INT(obj) == FIX2INT(array[index])) {
return Qtrue;
}
}

return Qfalse;
}

static VALUE t_at(VALUE self, VALUE obj)
{
return array[obj];
}

The value of "obj" comes from within Ruby (IRB, actually). But unless I
coerce it using FIX2INT, it's always a weird value. Say I put in 1,
it'll tell me it received 31478 or something similar.

Another thing... I thought that if I passed in an 'a' or any other
non-integer value into the add method, I'd get an error. I don't. In
fact...

require 'my_test'
mt = MyTest.new
mt.add('a', 0)
mt.at(0) # 'a'

But...

mt.include?('a') # false

So... what's happening to my arguments as they pass through Ruby and
into C?


Notice that you've declared the arguments to t_add as VALUEs, not ints.
This is because Ruby passes VALUE arguments to your function. A VALUE is
a reference to a Ruby object. If you want to store the arguments in an
int array, convert the objects to ints via FIX2INT or some other
conversion function:

static VALUE t_add(VALUE self, VALUE obj, VALUE i)
{
array[FIX2INT(i)] = FIX2INT(obj);
return self;
}

A VALUE can reference any Ruby object, but the FIX2INT macro expects its
argument to be a Ruby Fixnum. If it's not, it will raise an exception.
You'll get an exception if you call mt.add("a", 0).

Also, the return value from t_add should be a VALUE, not an int. It's
your choice which VALUE to return. Here I chose self.
 
M

Morris Keesan

Also: not a Ruby issue, but a C issue: You've left yourself wide open
for a buffer overflow. Unless your Ruby code somehow guarantees that
the value of i will always be within the range 0..999999 (in which
case it really should be documented, and probably in a Ruby CONSTANT
and C #define), it's inevitable that some day some code will pass in a
negative value, or a value of 1000000 or greater, and your code will
mysteriously stop working correctly. Make sure, every time you access
your array, but especially when assigning to it, that you check that
the index is valid.
 
D

Daniel Waite

Tim said:
A VALUE can reference any Ruby object, but the FIX2INT macro expects its
argument to be a Ruby Fixnum. If it's not, it will raise an exception.
You'll get an exception if you call mt.add("a", 0).

Ah, that seems obvious now. Thanks for the help.
Also, the return value from t_add should be a VALUE, not an int. It's
your choice which VALUE to return. Here I chose self.

Agreed, and I originally was returning self (it's still commented in the
actual file) but I was trying to strip out all unnecessary code.
Also: not a Ruby issue, but a C issue: You've left yourself wide open
for a buffer overflow.

Fully aware, but thanks for the concern. ;)

Brian said:
Have a look at the RubyInline gem, and FFI

I've looked at RubyInline, and it was surprisingly easy to get it
working. I'm uncertain if it will do what I need it do though -- namely,
pass information back and forth between C and Ruby.

The FFI project looks interesting though... :)

Thanks for the excellent replies.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,825
Latest member
VernonQuy6

Latest Threads

Top