Динамический массив с пустым указателем

Итак, изучая указатели в C, я подумал, что в качестве упражнения я мог бы сделать какой-то общий массив, и я заставил его работать при использовании void **, например так:

struct array{
    void **data;
    size_t size, capacity;
};

вставка таких элементов:

void array_append(array *a, void *element){
    if(a->size == a->capacity){
        a->capacity += ARRAY_GROW_CONSTANT;
        a->data = realloc(a->data, sizeof(void*)*a->capacity);
    }
    a->data[a->size++] = element;
}

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

a->data[a->size++] = element;

я бы сделал что-то вроде

a->data[a->size] = malloc(inserted_element_size);
memcpy(a->data[a->size], &element, inserted_element_size);
size++;

но я думал, что смогу получить ту же функциональность при использовании простого void* вместо void**

struct array{
    void *start;
    size_t element_size, size;
};

и вставка таких элементов, как

void array_append(array *a, void *element){
    a->size += 1;
    a->data = realloc(a->data, (a->size*a->element_size));
    memcpy(a->data + (a->size - 1)*a->element_size, &element, a->element_size);
}

но это приводит к segfault, и я не знаю, почему. Насколько я понимаю (очевидно, нет), указатели - это адреса в памяти, поэтому, если у меня есть непрерывный блок памяти, я могу хранить в нем переменную любого типа со смещением.

Редактировать: Спасибо за объяснение, это действительно помогло.

Чем инициализируется a->data?

Я использовал функцию для инициализации массива, и a->data был инициализирован до element_size.

вызывающий должен будет преобразовать результат в элемент *

Я думал, что могу использовать макрос, чтобы сделать ввод короче (я думаю, это плохо?), но я не знаю о производительности приведения типов от void* до struct*.

Создание динамического массива элементов напрямую кажется мне более практичным.

Но это не позволит мне использовать массив как универсальный? Я хотел определить общий массив, который я мог бы использовать для хранения любого типа, например

array *a1 = create_array(sizeof(int)); // array of int
array *a2 = create_array(sizeof(double)); // array of double
etc...

почему вы хотите, чтобы ваши данные хранились в непрерывном блоке?

Потому что я думал, что вам нужен непрерывный блок памяти, чтобы использовать memcpy со смещением.


person user3166486    schedule 06.01.2014    source источник


Ответы (2)


Чем инициализируется a->data? Чтобы это работало, он должен быть установлен в NULL при создании (пустого) массива.

Кроме того, при вычислении адреса не учитывается арифметика указателей. a->data — это указатель (на void *), поэтому смещение (a->size - 1)*a->element_size будет умножено на размер указателя (на void *). Установка a->data в void * должна вызвать ошибку компилятора, поскольку void не имеет размера.

Если вы действительно хотите это сделать, лучше объявите a->data как char *, размер которого гарантированно будет равен 1.

Осторожно: для доступа к вашему массиву потребуется приведение к (element*). Это предотвратит использование квадратных скобок.
Вам нужно будет предоставить функцию доступа, такую ​​как void * array_at(size_t index) { return &a->data[index*a->element_size]; }
, а затем вызывающая сторона должна будет преобразовать результат в element *.

Создание динамического массива из elements напрямую кажется мне более практичным.
Вы все еще можете вызывать realloc для него, если хотите.

Но первый вопрос, который приходит мне на ум: почему вы хотите, чтобы ваши данные хранились в непрерывном блоке?

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

person kuroi neko    schedule 06.01.2014

Комментируя последнюю часть кода, используя void* в качестве массива данных. Код должен работать, но есть проблемы:

Вы передаете адрес указателя элемента, а не только сам указатель, который уже указывает на правильные (надеюсь) данные.

Также вы не можете выполнять арифметику указателя на void, но некоторые компиляторы это позволяют.

Правильная версия memcpy будет

memcpy ( ( unsigned char* )a->data + (a->size - 1)*a->element_size, element, a->element_size);
person this    schedule 06.01.2014