Я хочу реализовать свой собственный класс статического массива в 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 ... Я не могу понять, что здесь происходит?
Интерфейс 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
Однако я все еще получаю довольно неожиданное поведение… пример: arr = StaticArray.new(4) arr.print 0 -268435456 -1600786800 2047
что я здесь делаю неправильно?
malloc
не гарантирует, что память заполнена нулями и может что-то остаться от предыдущего использования памяти. Похоже, это может быть так.
Так что я не понимаю, для чего нужен malloc... если при использовании malloc(n) 1) вам не гарантируется, что у вас есть n свободного места и 2) вы на самом деле не вызываете ошибку, если пытаетесь записать по смещению, которое больше, чем то, на что вы повлияли с помощью malloc (C не предоставляет никаких спецификаций, касающихся проблемы доступа к недопустимому индексу). Тогда в чем преимущество использования malloc по сравнению с инициализацией случайного указателя....
1) Если malloc(n)
возвращает ненулевой указатель, гарантируется (в основном, см., например, убийцу OOM в Linux), что у вас есть n байтов. Но он не обещает установить для этих байтов какое-либо конкретное значение (например, 0) по соображениям производительности (одна из распространенных причин использования C). Вы можете использовать скажем memset
, если хотите обнулить его. Из соображений безопасности в наши дни свежий (еще не использованный конкретным процессом) из ОС обнуляется (поэтому вы не можете видеть, есть ли в нем пароль, скажем, от Chrome), но после того, как процесс создает / уничтожает несколько вещи malloc будут повторно использовать память, заполненную вещами.
Какова ваша цель здесь? Производительность, уменьшенное использование памяти, взаимодействие с другим нативным кодом?