Search code examples
carraysrubymemoryffi

Creating a static array class in ruby with ruby FFI


I want to implement my own static array class in ruby. It will be an array with a fixed capacity and all elements in the array will be of a single type. In order to have direct access to memory, I am using the FFI gem https://github.com/ffi/ffi which enables to create your own C functions and use them in your ruby program. I created a very simple C function that allocates memory for an array of integer and returns a pointer to the memory space :

int * create_static_array(int size) {
  int *arr = malloc(size * sizeof(int));
  return arr;
}

This is my ruby static_array class that uses create_static_array :

require 'ffi'
class StaticArray
  attr_accessor :pointer, :capacity, :next_index
  extend FFI::Library

  ffi_lib './create_array/create_array.so'
  attach_function :create_static_array, [:int], :pointer

  def initialize(capacity)
    @capacity = capacity
    @pointer = create_static_array(capacity)
    @next_index = 0
  end
 # adds value to the next_index in array
  def push(val)
    @pointer[@next_index].write(:int, val)
    @next_index += 1
  end
 # reads value at index
  def [](index)
    raise IndexOutOfBoundException if index >= @capacity
    self.pointer[index].read(:int)
  end
 # print every value in index
  def print
    i = 0
    while (i <   @capacity)
      puts @pointer[i].read(:int)
      i += 1
    end
  end
end

I added a couple methods to interact with my array, push elements, read elements at index... However my static_array instances are not exactly working as expected...

Let's say I write :

// creates a static array in memory which can store 4 ints
arr = StaticArray.new(4)

now let's push an int in our arr :

arr.push(20)

arr.print will output

20
0
0
0

which makes sense. Now let's push another int into arr :

arr.push(16)

and arr.print again :

4116
16
0
0

20 has been replaced by 4116 ... I cant really get what is going on here ?

Here's the link to the FFIPointer class doc if that helps https://www.rubydoc.info/github/ffi/ffi/FFI/Pointer


Solution

  • The FFI interface doesn't know about the type of your pointer, so it is just treating it as a byte array (see initialize on the pointer type). Note that while you do pass :int, this is to the specific write and read, not where you are doing the indexing. Thus you are writing and printing at byte offsets 0,1,2,3 rather than the integer elements at 0,4,8,12.

    On a little endian system, with a 32bit, 4 byte int, the binary value of 20 is 14 00 00 00 and 16 is 10 00 00 00.

    So you allocate 4*4 bytes, so 32 bytes, the first 8 being.

    00 00 00 00 00 00 00 00
    

    And write 20 at offset 0

    14 00 00 00 00 00 00 00
    

    And then write 16 at offset 1

    14 10 00 00 00 00 00 00
    

    14 10 00 00 is 0x00001014 or 4116, and then at the next offset you print it is 10 00 00 00 which is 16.