создание класса динамического массива в ruby ​​с использованием функций FFI и C - PullRequest
7 голосов
/ 31 марта 2019

Я хотел бы создать свой собственный класс динамического массива в 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;

}

aФайл create_array.h (из того, что я понял о FFI, вам нужно поместить ваши функции c в библиотеку ac.):

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 ]

7 голосов
/ 08 апреля 2019

Похоже, вы не работаете с кодом 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 голос
/ 04 апреля 2019

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

#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 - это эквивалент C объекта Ruby (обычно он отображается на тегированный указатель, преобразованный в 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 может оптимизировать этап копирования, что очень полезно в больших массивах и оказывает значительное положительное влияние на производительность. Это можно сделать, используя тот факт, что адреса памяти являются виртуальными и не должны отображаться в непрерывные сегменты памяти. то есть та же страница памяти может получить новый адрес как часть нового выделения.

...