2011-04-25 10 views
6

plain C ma ładną funkcję - wskaźniki typu void, które mogą służyć jako wskaźnik do dowolnego typu danych.
Ale załóżmy, że mam następujące struktury:Tablica typu void


struct token { 
    int type; 
    void *value; 
}; 

gdzie pole wartość może wskazywać na tablicy char lub int, lub coś innego.
Przy przydzielaniu nowej instancji tej struktury potrzebuję:

1) przydzielić pamięć dla tej struktury;
2) przydzielić pamięć dla wartości i przypisać ją do pola wartości.

Moje pytanie brzmi - czy można zadeklarować "tablicę typu void", która może być rzutowana na dowolny inny typ, np. Pustą wskazówkę?

Wszystko, czego chcę, to użycie "elastycznej tablicy elementów" (opisanej w 6.7.2.1 standardu C99) z możliwością rzutowania na dowolny typ.

coś takiego:


struct token { 
    int type; 
    void value[]; 
}; 

struct token *p = malloc(sizeof(struct token) + value_size); 
memcpy(p->value, val, value_size); 
... 
char *ptr = token->value; 

Przypuszczam deklarując token-> jako wartość int lub char tablicy i rzucając do potrzebnego typu później zrobią tej pracy, ale może być bardzo mylące dla kogoś, kto przeczyta ten kod później.

+0

Podwójne wskaźniki? –

+2

używając 'char []' jest w porządku imho, ponieważ 'sizeof (char) == 1' i nigdy nie będziesz zaskoczony. Możesz rozważyć makra, aby uzyskać dostęp do 'p-> value' z poprawnym typem. –

Odpowiedz

1

Prawdopodobnie to zrobić:

struct token { 
    int type; 
    void *value; 
}; 

struct token p; 

p.value = malloc(value_size); 

p.value[0] = something; 
p.value[1] = something; 
... 

edytować rzeczywiście trzeba typecast te p.value [indeks] = somethings. I/lub użyj unii, aby nie musieć typować.

+0

Musiałbyś rzucić 'p.value' zanim go użyjesz. –

+0

'p.value [0] = something' przyniesie z pewnością błąd podczas kompilacji? Nie można wykluczyć "pustki". –

+0

uśmiechnąłeś się, pokonałeś mnie ... próbowałem szybko wprowadzić edycję. –

3

Cóż, coś, ale nie jest to prawdopodobnie coś chcesz:

struct token { 
    // your fields 
    size_t item_size; 
    size_t length 
}; 

struct token *make_token(/* your arguments */, size_t item_size, size_t length) 
{ 
    struct token *t = malloc(sizeof *t + item_size * length); 
    if(t == NULL) return NULL; 
    t->item_size = item_size; 
    t->length = length; 
    // rest of initialization 
} 

Poniższe makro mogą być używane do indeksowania danych (zakładając x jest struct token *):

#define idx(x, i, t) *(t *)(i < x->length ? sizeof(t) == x->item_size ? 
         (void *)(((char *)x[1]) + x->item_size * i) 
        : NULL : NULL) 

I , jeśli chcesz, poniższe makro może owijać twoją funkcję make_token, aby było trochę bardziej intuicyjne (lub bardziej hackowe, jeśli myślisz o tym w ten sposób):

#define make_token(/* args */, t, l) (make_token)(/* args */, sizeof(t), l) 

Zastosowanie:

struct token *p = make_token(/* args */, int, 5); // allocates space for 5 ints 
... 
idx(p, 2, int) = 10; 
1

Nie można mieć tablicę „pustych” przedmiotów, ale powinieneś być w stanie zrobić coś takiego, co chcesz, tak długo, jak wiesz value_size kiedy wykonać malloc. Ale to nie będzie piękne.

struct token { 
     int type; 
     void *value;  
};  

value_size = sizeof(type)*item_count; 
struct token *p = malloc(sizeof(struct token) + value_size); 
//can't do memcpy: memcpy(p->value, val, value_size); 
//do this instead 
type* p = (type*)&(p->value); 
type* end = p+item_count; 
while (p<end) { *p++ = someItem; } 

Pamiętaj, że potrzebujesz dodatkowego adresu operatora, aby uzyskać dodatkowe miejsce.

type *ptr = (type*)&(token->value); 

Będzie to „odpady” sizeof (void *) bajtów, a oryginalny typ value naprawdę nie ma znaczenia, więc równie dobrze możesz użyć mniejszego elementu. Najprawdopodobniej uzyskałabym typedef char placeholder; i wykonałbym ten typ w postaci value.

0

Następująca struktura może ci pomóc.

struct clib_object_t { 
void* raw_data; 
size_t size; 
}; 

struct clib_object_t* 
new_clib_object(void *inObject, size_t obj_size) { 
    struct clib_object_t* tmp = (struct clib_object_t*)malloc(sizeof(struct clib_object_t)); 
    if (! tmp) 
     return (struct clib_object_t*)0; 
    tmp->size  = obj_size; 
    tmp->raw_data = (void*)malloc(obj_size); 
    if (!tmp->raw_data) { 
     free (tmp); 
     return (struct clib_object_t*)0; 
    } 
    memcpy (tmp->raw_data, inObject, obj_size); 
    return tmp; 
} 

clib_error 
get_raw_clib_object (struct clib_object_t *inObject, void**elem) { 
    *elem = (void*)malloc(inObject->size); 
    if (! *elem) 
     return CLIB_ELEMENT_RETURN_ERROR; 
    memcpy (*elem, inObject->raw_data, inObject->size); 

    return CLIB_ERROR_SUCCESS; 
} 

Więcej szczegółów: clibutils

0

Rozwijając na odpowiedź AShelly mogą to zrobić;

/** A buffer structure containing count entries of the given size. */ 
typedef struct { 
    size_t size; 
    int count; 
    void *buf; 
} buffer_t; 

/** Allocate a new buffer_t with "count" entries of "size" size. */ 
buffer_t *buffer_new(size_t size, int count) 
{ 
    buffer_t *p = malloc(offsetof(buffer_t, buf) + count*size); 
    if (p) { 
     p->size = size; 
     p->count = count; 
    } 
    return p; 
} 

Uwaga stosowanie "offsetof()" zamiast "sizeof()" podczas przydzielania pamięci, aby uniknąć marnowania "void * buf;" rozmiar pola. Typ "buf" nie ma większego znaczenia, ale użycie "void *" oznacza, że ​​pole "buf" w strukturze będzie optymalnie dopasowane do wskaźnika, dodając w razie potrzeby dopełnienie. Zwykle zapewnia to lepsze wyrównanie pamięci dla wpisów, zwłaszcza jeśli są one co najmniej tak duże jak wskaźnik.

Dostęp do wpisów w buforze wygląda następująco;

/** Get a pointer to the i'th entry. */ 
void *buffer_get(buffer_t *t, int i) 
{ 
    return &t->buf + i * t->size; 
} 

Zwróć uwagę na dodatkowy adres operatora, aby uzyskać adres pola "buf" jako punkt początkowy dla przydzielonej pamięci wejścia.