Прехвърляне на празен указател към произволен тип указател

Опитвам се да направя своя собствена реализация на общи структури от данни в C за моя собствена полза от обучението. Сегашното ми усилие е вектор и искам той да може да поддържа един произволен тип (или поне размер на типа, но не е ли това всичко, което наистина има значение в C?). Моята структура е следната:

struct vector
{
    void *item;
    size_t element_size;
    size_t num_elements;
}

Това, което обаче не разбирам, е как мога да се позова на конкретни елементи в масива *item, ако типът трябва да е произволен. Знам element_size, но това не ми помага при рефериране на индекс (напр. item[5]), защото void не е тип. Реших, че ще бъде най-лесно да се отнасят към елементите като отмествания в байтове. Така че, ако държах вектор от структури с размер 12, item[5] ще бъде на 12*5=60 байта от item*. Не разбирам обаче как да извлека тези данни. Знам, че искам 12 байта от item+60, но как да накарам компилатора да разбере това? Навлизам ли в територията на препроцесора?


person doubleshot    schedule 30.01.2012    source източник


Отговори (3)


sizeof е мярка в символи, така че можете да направите това:

void *start_of_sixth_element = ((char*)item) + (5 * element_size);

Някой, който знае какъв е типът на всеки елемент, може след това да прехвърли start_of_sixth_element към правилния тип, за да го използва.

void* е лош избор в известен смисъл, тъй като не можете да правите аритметика на указател с void* указатели в стандартния C (има разширение на GNU, което го позволява, но за преносим код използвате char* или unsigned char* поне за аритметика).

Кодът, който знае правилния тип преди да приложи отместването 5, може просто да направи това:

correct_type *sixth_element = ((correct_type *)item) + 5;

void не е тип

Това е тип, просто не е "пълен тип". „Непълен тип“ до голяма степен означава, че компилаторът не знае към какво наистина сочи void*.

person Steve Jessop    schedule 30.01.2012
comment
При даден тип има ли лесен начин да откриете правилната стойност на element_size? (Мисля изисквания за подравняване.) - person NPE; 30.01.2012
comment
@aix: като се има предвид типът, element_size е sizeof(type): размерът на обект вече включва всички подложки, необходими за подравняване. Подравняването зависи от това как е разпределена паметта, към която сочи item. Докато е разпределено с malloc(element_size*num_elements) (и умножението не препълва!), тогава няма проблеми с подравняването, тъй като malloc гарантирано ще върне паметта, подравнена за всеки обект. Използвам хилещи се кавички, защото типовете със супер размер за SIMD инструкции често се изключват от реализациите. - person Steve Jessop; 30.01.2012
comment
@aix, операторът sizeof взема предвид подравняването. Например, ако long има 4-байтово подравняване, тогава sizeof, приложено към struct, съдържащо long и char, е 8. - person Lindydancer; 30.01.2012
comment
Вместо това ми беше препоръчано да използвам unsigned int като мой вътрешен тип item[], който ще съдържа адреси на стойностите, които контейнерът съхранява. Бихте ли препоръчали това вместо да използвате void*? - person doubleshot; 30.01.2012
comment
@doubleshot: не, не е възможно да се съхрани стойност на указател в unsigned int. На 64-битов Windows или Linux адресът е 64-битов, но unsigned int е само 32-битов, така че е напълно нестартерен. void* и unsigned char* могат разумно да се използват за съхраняване на адреса на всеки обект. - person Steve Jessop; 30.01.2012
comment
Благодаря ви - постигнах успех с използването на void** като мой вътрешен масив от елементи. Оценявам времето ви. - person doubleshot; 07.02.2012

void * е общ тип указател за всеки тип указател на обект. Трябва да знаете към какъв тип сочи, за да предадете правилно void * и да дереферирате указателя.

Ето пример с обект void *, използван с различни типове указатели.

void *p;
int a = 42, b;
double f = 3.14159, g;

p = &a;
b = *(int *) a;

p = &f;
g = *(double *) f; 
person ouah    schedule 30.01.2012

Така че, ако държах вектор от структури с размер 12, item[5] ще бъде на 12*5=60 байта от item*. Не разбирам обаче как да извлека тези данни. Знам, че искам 12 байта от item+60, но как да накарам компилатора да разбере това?

Кажете, че вашият даден тип е foo. Както вече разбрахте, трябва да достигнете до адреса в item + (5 * 12), след което да дереферирате празния указател, след като го прехвърлите към foo*.

foo my_stuff = *(foo *)(item + 5 * 12);

Можете също да използвате sizeof(), ако можете да определите по време на компилиране типа на вашите данни:

foo my_stuff = *(foo *)(item + 5 * sizeof(foo));
person Seki    schedule 30.01.2012