Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

array of type void

plain C have nice feature - void type pointers, which can be used as pointer to any data type.
But, assume I have following struct:


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

where value field may point to char array, or to int, or something else.
So when allocating new instance of this struct, I need:

1) allocate memory for this struct;
2) allocate memory for value and assign it to value field.

My question is - is there ways to declare "array of type void", which can be casted to any another type like void pointer?

All I want is to use "flexible member array" (described in 6.7.2.1 of C99 standard) with ability to casting to any type.

Something like this:


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;

I suppose declaring token->value as char or int array and casting to needed type later will do this work, but can be very confusing for someone who will read this code later.

like image 404
S.J. Avatar asked Apr 25 '11 22:04

S.J.


2 Answers

Well, sort of, but it's probably not something you want:

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
}

The following macro can be used to index your data (assuming x is a 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)

And, if you like, the following macro can wrap your make_token function to make it a little more intuitive (or more hackish, if you think about it that way):

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

Usage:

struct token *p = make_token(/* args */, int, 5); // allocates space for 5 ints
...
idx(p, 2, int) = 10;
like image 143
Chris Lutz Avatar answered Sep 19 '22 20:09

Chris Lutz


Expanding on AShelly's answer you can do this;

/** 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;
}

Note the use of "offsetof()" instead of "sizeof()" when allocating the memory to avoid wasting the "void *buf;" field size. The type of "buf" doesn't matter much, but using "void *" means it will align the "buf" field in the struct optimally for a pointer, adding padding before it if required. This usually gives better memory alignment for the entries, particularly if they are at least as big as a pointer.

Accessing the entries in the buffer looks like this;

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

Note the extra address-of operator to get the address of the "buf" field as the starting point for the allocated entry memory.

like image 36
Donovan Baarda Avatar answered Sep 19 '22 20:09

Donovan Baarda