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

И така, изучавайки указатели в 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() ще натовари разпределителя на паметта, ще загуби време за правене на копия и евентуално ще фрагментира купчината дори повече от колекция от отделни mallocs.

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