создание класса динамического массива в ruby с использованием FFI и функции C.

#c #ruby #ffi

#c #ruby #ffi

Вопрос:

Я хотел бы создать свой собственный класс динамического массива в ruby (в качестве обучения). Идея состоит в том, чтобы иметь класс DynamicArray, который имеет емкость (количество элементов, которые он может содержать в данный момент), размер (количество элементов, которые фактически были помещены в массив в данный момент) и static_array, который представляет собой статический массив целых чисел фиксированного размера. Всякий раз, когда этот static_array заполнится, мы создадим новый статический массив с удвоенной емкостью исходного static_array и скопируем все элементы внутри нового static_array. Поскольку в ruby нет статического массива, моей идеей было использовать FFI https://github.com/ffi/ffi . создать функцию на c, которая создает статический массив int размером n, а затем сможет использовать его в моей программе 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
  

Не могли бы вы, пожалуйста, объяснить мне, что я должен сделать, чтобы это заработало?

Комментарии:

1. Я не уверен, что это хороший вариант использования, но чтобы мы могли более четко понимать, что пытается сделать ваш код, не могли бы вы написать несколько модульных тестов для ваших методов?

2. По сути, мой класс динамического массива удваивает емкость массива всякий раз, когда массив (A) заполнен. Итак, add — это добавление элемента в A. Resize_array — это создание нового статического массива с вдвое большей емкостью, чем A. Затем он копирует все элементы из A в new_arr и влияет на new_arr в A. create_array — это функция C, которая создает static_array из n элементов типа integer. Я хотел бы импортировать create_array в свою программу ruby благодаря FFI

3. @lacostenycoder Я пытался добавить больше информации, пожалуйста, скажите мне, если это все еще неясно

4. @DavidGeismar Вы пытаетесь использовать файл заголовка с ffi_lib . Вместо этого вам нужно передать совместно используемую библиотеку с функцией.

5. Вам не обязательно использовать FFI (кстати, я не совсем понимаю, что это такое); что вы могли бы сделать, так это подкласс Array и добавить / переопределить функции в соответствии с требованиями DynamicArray !??

Ответ №1:

Кажется, что вы неправильно работаете с кодом 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.

Наконец, вы можете использовать функцию C в ruby с некоторыми изменениями в вашем 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. Я думаю, что это самый полный ответ на данный момент. Я использовал довольно похожий подход, но я решил не реализовывать функцию add_to_array в C. Что я решил сделать, так это реализовать класс StaticArray в ruby, который использует FFIPointer, возвращаемый из функции create_array C. Класс FFIPointer rubydoc.info/github/ffi/ffi/FFI/Pointer имеет пару методов, таких как read и write, которые фактически выполняют то, что делает ваша функция add_to_array

2. Пожалуйста, обратите внимание, что array of int может быть не тем, что вы хотите. Такой массив может содержать числа (до определенного момента), но он не может безопасно размещать объекты Ruby (для этого используйте VALUE , uintptr_t или void * ).

Ответ №2:

Следующая функция в вашем вопросе не выделяет нужный массив:

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

}
  

Объект array в функции, которую вы написали, автоматически выделяется в стеке и автоматически уничтожается, как только функция возвращается. Фактически, поскольку он остался неиспользуемым, компилятор 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;
  

Вам все еще нужно подключить код C к слою Ruby с помощью FFI, но это должно ответить на ваш вопрос о том, как изменить размер массива (и исправить вашу ошибку при его создании).

Примечание: При больших выделениях realloc может оптимизировать этап копирования, что очень полезно в больших массивах и оказывает значительное положительное влияние на производительность. Это может быть выполнено за счет использования того факта, что адреса памяти являются виртуальными и их не нужно сопоставлять с непрерывными сегментами памяти. т.Е. та же страница памяти может получить новый адрес как часть нового выделения.