Создание класса статического массива в ruby ​​с помощью ruby ​​FFI

Я хочу реализовать свой собственный класс статического массива в ruby. Это будет массив с фиксированной емкостью, и все элементы массива будут одного типа. Чтобы иметь прямой доступ к памяти, я использую гем FFI https://github.com/ffi/ffi, который позволяет создавать ваши собственные функции C и использовать их в вашей программе ruby. Я создал очень простую функцию C, которая выделяет память для массива целых чисел и возвращает указатель на область памяти:

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

Это мой класс ruby ​​static_array, который использует 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

Я добавил пару методов для взаимодействия с моим массивом, push-элементов, чтения элементов по индексу... Однако мои экземпляры static_array работают не совсем так, как ожидалось...

Допустим, я пишу:

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

теперь давайте вставим int в наш arr :

arr.push(20)

arr.print выведет

20
0
0
0

что имеет смысл. Теперь давайте вставим в arr еще один int:

arr.push(16)

и еще раз arr.print:

4116
16
0
0

20 был заменен на 4116 ... Я не могу понять, что здесь происходит?

Вот ссылка на документ класса FFIPointer, если это поможет https://www.rubydoc.info/github/ffi/ffi/FFI/Pointer

Какова ваша цель здесь? Производительность, уменьшенное использование памяти, взаимодействие с другим нативным кодом?

Fire Lancer 11.04.2019 15:04
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
340
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Интерфейс FFI не знает о типе вашего указателя, поэтому он просто обрабатывает его как массив байтов (см. инициализацию типа указателя). Обратите внимание, что, хотя вы передаете :int, это относится к конкретным write и read, а не к тому месту, где вы выполняете индексацию. Таким образом, вы пишете и печатаете со смещением в байтах 0,1,2,3, а не целыми элементами со смещением 0,4,8,12.

В системе с прямым порядком байтов, с 32-битным, 4-байтовым целым, двоичное значение 20 равно 14 00 00 00, а 16 — 10 00 00 00.

Таким образом, вы выделяете 4 * 4 байта, то есть 32 байта, первые 8 из которых.

00 00 00 00 00 00 00 00

И напишите 20 по смещению 0

14 00 00 00 00 00 00 00

А затем напишите 16 по смещению 1

14 10 00 00 00 00 00 00

14 10 00 00 это 0x00001014 или 4116, а затем при следующем смещении вы печатаете 10 00 00 00, что равно 16.

Итак, я инициализировал массив из 16 байтов, и мой указатель не знает тип инициализированного указателя, поэтому, когда я пытаюсь получить доступ к определенному значению через индекс, я фактически обращаюсь к определенному байту. Затем, поскольку я указываю тип значения, которое я хочу прочитать (read(:int)), указатель понимает, что я хочу прочитать все 4 байта, начиная с него, и вычислить целое число. Из ваших объяснений я понял, что для того, чтобы моя программа заработала, я должен умножить всю свою индексацию на коэффициент 4. Репозиторий Github

David Geismar 15.04.2019 11:18

Однако я все еще получаю довольно неожиданное поведение… пример: arr = StaticArray.new(4) arr.print 0 -268435456 -1600786800 2047 что я здесь делаю неправильно?

David Geismar 15.04.2019 11:18
malloc не гарантирует, что память заполнена нулями и может что-то остаться от предыдущего использования памяти. Похоже, это может быть так.
Fire Lancer 15.04.2019 11:34

Так что я не понимаю, для чего нужен malloc... если при использовании malloc(n) 1) вам не гарантируется, что у вас есть n свободного места и 2) вы на самом деле не вызываете ошибку, если пытаетесь записать по смещению, которое больше, чем то, на что вы повлияли с помощью malloc (C не предоставляет никаких спецификаций, касающихся проблемы доступа к недопустимому индексу). Тогда в чем преимущество использования malloc по сравнению с инициализацией случайного указателя....

David Geismar 15.04.2019 17:24

1) Если malloc(n) возвращает ненулевой указатель, гарантируется (в основном, см., например, убийцу OOM в Linux), что у вас есть n байтов. Но он не обещает установить для этих байтов какое-либо конкретное значение (например, 0) по соображениям производительности (одна из распространенных причин использования C). Вы можете использовать скажем memset, если хотите обнулить его. Из соображений безопасности в наши дни свежий (еще не использованный конкретным процессом) из ОС обнуляется (поэтому вы не можете видеть, есть ли в нем пароль, скажем, от Chrome), но после того, как процесс создает / уничтожает несколько вещи malloc будут повторно использовать память, заполненную вещами.

Fire Lancer 15.04.2019 17:33

Другие вопросы по теме