使用FFI和C函数在ruby中创建一个动态数组类

时间:2019-03-31 12:00:07

标签: c ruby ffi

我想在ruby中创建自己的动态数组类(作为培训)。 这个想法是要创建一个DynamicArray类,它具有一个容量(在给定时刻它可以容纳的元素数量),一个大小(在给定时刻实际被推入数组的元素数量)和一个static_array,它是固定大小的int静态数组。每当此static_array满时,我们将创建一个容量为原始static_array两倍的新静态数组,并复制新static_array内部的每个元素。 由于ruby中没有静态数组,所以我的想法是使用FFI https://github.com/ffi/ffi。在c中创建一个函数,该函数创建一个大小为n的int静态数组,然后可以在我的ruby程序中使用它。 我对C的知识很少,并且很难理解FFI的文档 到目前为止,这就是我创建的create_array.c文件,该文件定义了我的c函数以创建数组。

#include<stdio.h>
int * createArray ( int size )
{
  int array[size];
  return 0;

}

一个create_array.h文件(据我对FFI的了解,您需要将c函数放入c库中。):

int * createArray ( int size )

这是我的dynamic_array.rb文件,可以按照以下方式进行操作:

require 'ffi'
class DynamicArray
  extend FFI::Library
  ffi_lib "./create_array.h"
  attach_function :create_array, [:int], :int
  def initialize
    @size = 0
    @capacity = 1
    @current_index = 0
    @static_array = create_array(@capacity)
  end

  def add(element)
    @size += 1
    resize_array if @size > @capacity
    @static_array[@current_index] = element
    @current_index += 1
  end

  private

  def resize_array
    @capacity = @capacity*2
    new_arr = create_array(@capacity)
    @static_array.each_with_index do |val, index|
      new_arr[index] = val
    end
    @static_array = new_arr
  end
end

这里有一些添加和调整大小的测试:

  def test_add
    dynamic_arr = DynamicArray.new
    dynamic_arr.add(1)
    dynamic_arr.add(2)
    assert_equal(1, dynamic_arr.static_array[0])
    assert_equal(2, dynamic_arr.static_array[1])
  end

  def test_resize_array
    dynamic_arr = DynamicArray.new
    dynamic_arr.add(1)
    dynamic_arr.add(2)
    assert_equal(2, dynamic_arr.capacity)
    dynamic_arr.resize_array
    assert_equal(4, dynamic_arr.capacity)
    assert_equal
  end

你能解释一下我应该做些什么吗?

2 个答案:

答案 0 :(得分:7)

似乎您没有正确使用C代码。

create_array C函数中:

  • 您没有返回数组,因此ruby代码无法与新创建的数组一起使用,您需要将其返回
  • 如果要返回数组,则实际上需要返回它的指针
  • 在C语言中,为了创建一个数组并且在编译之前不知道大小,您需要使用malloc(或alloc系列中的其他函数)分配它的内存

将所有内容放在一起,这就是您的create_array.c文件的样子:

#include <stdlib.h> /* in order to use malloc */

int * create_array (int size){
  int *a = malloc(size * sizeof(int));
  return a; /* returning the pointer to the array a*/
}

和您的头文件create_array.h

int * create_array(int);

要封装所有内容,您仍然需要先进行编译,然后才能使ruby接触到它:

gcc -shared -o create_array.so -fPIC create_array.c

此命令正在使用gcc将C代码从create_array.so源文件编译到名为create_array.c的共享库中。需要安装gcc才能使其正常工作。

最后,您可以在ruby中使用C函数,并在dynamic_array.rb中进行一些修改:

require 'ffi'
class DynamicArray
  extend FFI::Library
  ffi_lib "./create_array.so" # using the shared lib
  attach_function :create_array, [:int], :pointer # receiving a pointer to the array
  # rest of your code

现在,这应该可以工作! 但是您的ruby代码仍然存在一些问题:

  • 执行@static_array = create_array(@capacity)时,您将收到指向分配的数组的C指针,而不是数组本身,至少不是在ruby中。
  • 编写@static_array[@current_index] = element无效NoMethodError: undefined method '[]=' for #<FFI::Pointer address=0x000055d50e798600>
  • 如果要将元素添加到数组,则C代码必须执行此操作。像这样:
void add_to_array (int * array, int index, int number){
  array[index] = number;
}
attach_function :add_to_array, [:pointer, :int, :int], :void
add_to_array(@static_array, @current_index, element)
  • @static_array.each_with_index也一样,您需要用C编写代码。

答案 1 :(得分:1)

问题中的以下函数分配您想要的数组:

#include<stdio.h>
int * createArray ( int size )
{
  int array[size];
  return 0;

}

您编写的函数中的数组对象会自动分配到堆栈上,一旦函数返回,它将自动销毁。实际上,由于不使用它,C编译器可能会优化数组。

您可能希望做的是:

VALUE * create_array(size_t size) {
   VALUE * a = calloc(size, sizeof(*a));
   return a;
}

现在,返回的aVALUE的数组(或者从技术上讲,是指向数组第一个成员的指针)。

VALUE是Ruby对象的C等效项(通常将映射到转换为unsigned long的标记指针)。

调整大小可以使用realloc,它会自动将现有数据复制到新的内存(或数组)中:

VALUE * tmp = realloc(tmp, new_size * sizeof(*a));
if(!tmp) {
   /* deal with error, print message, whatever... */
   free(a);
   exit(-1);
}
a = tmp;

您仍然需要通过FFI将C代码连接到Ruby层,但这应该回答您有关如何调整数组大小(并纠正有关创建数组的错误)的问题。

注意:对于较大的分配,重新分配可以优化复制阶段,这在大型阵列中非常有好处,并且对性能有很大的积极影响。这可以通过利用以下事实来执行:内存地址是虚拟的,不必映射到连续的内存段。也就是说,同一内存页可能会在新分配中收到新地址。