【问题标题】:creating a Dynamic array class in ruby using FFI and C function使用 FFI 和 C 函数在 ruby​​ 中创建动态数组类
【发布时间】:2019-03-31 12:00:07
【问题描述】:

我想在 ruby​​ 中创建自己的动态数组类(作为培训)。 这个想法是有一个类 DynamicArray ,它有一个容量(它在给定时刻可以容纳的元素数量)、一个大小(在给定时刻实际推入数组中的元素数量)和一个 static_array ,它是一个固定大小的静态整数数组。每当这个 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

你能解释一下我应该怎么做才能完成这项工作吗?

【问题讨论】:

  • 我不确定这是一个好的用例,但是为了让我们更清楚地了解您的代码要做什么,您能为您的方法编写一些单元测试吗?
  • 基本上,只要数组 (A) 已满,我的动态数组类就会将数组容量加倍。所以 add 是向 A 添加一个元素。 Resize_array 正在创建一个容量是 A 两倍的新静态数组。然后它将所有元素从 A 复制到 new_arr 并将影响 new_arr 到 A。 create_array 是创建 static_array 的 C 函数n 个整数类型的元素。感谢 FFI,我想将 create_array 导入到我的 ruby​​ 程序中
  • @lacostenycoder 我尝试添加更多信息,如果仍然不清楚,请告诉我
  • @DavidGeismar 您正在尝试使用带有ffi_lib 的头文件。相反,您需要使用该函数传递一个共享库。
  • 您不一定需要使用FFI(我不太清楚那是什么BTW);你可以做的是子类 Array 和添加/覆盖函数以满足 DynamicArray 的要求!??

标签: c ruby ffi


【解决方案1】:

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

create_arrayC 函数中:

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

总而言之,这就是您的 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.c 源文件的名为 create_array.so 的共享库中。需要安装 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 #&lt;FFI::Pointer address=0x000055d50e798600&gt;
  • 如果要向数组中添加元素,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 中对此进行编码。

【讨论】:

  • 我认为这是迄今为止最完整的答案。我采用了非常相似的方法,但我决定不在 C 中实现 add_to_array 函数。我选择在 ruby​​ 中实现一个 StaticArray 类,它使用从 create_array C 函数返回的 FFIPointer。 FFIPointer 类 rubydoc.info/github/ffi/ffi/FFI/Pointer 有几个方法,比如 read 和 write,它们实际上执行了 add_to_array 函数正在做的事情
  • 请注意,int 的数组可能不是您想要的。这样的数组可以保存数字(直到某个点),但它不能安全地容纳 Ruby 对象(使用VALUEuintptr_tvoid *)。
【解决方案2】:

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

#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;
}

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

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 层,但这应该可以回答您关于如何调整数组大小的问题(并修正您在创建数组时的错误)。

注意:对于大分配,realloc 可以优化复制阶段,这在大数组中非常有用,并且具有显着的积极性能影响。这可以通过利用内存地址是虚拟的并且不必映射到连续的内存段这一事实来执行。即,同一个内存页面可以接收一个新地址作为新分配的一部分。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-04
    • 1970-01-01
    • 2011-06-09
    • 2018-01-13
    相关资源
    最近更新 更多