0

Good morning,

I am trying to write in C a dynamic array that can accept different types.

The main structure is built as follows:

typedef struct st_array
{
   void* array;
   uint16_t size;
   uint16_t used;

   void  ( *push )( struct st_array*, void* );
   void* ( *at   )( struct st_array*, uint16_t );
} st_array;

void pushInArray( st_array* this, void* item )
{
    // Check for last occupied bin
    // if equal size, realloc() and increase this->size
    this->array[this->used++] = item;
}

void* getAt( st_array* this, utf16_t idx )
{
    if( idx <= this->used )
        return this->array[idx];
    return NULL;
}

st_array NewArray( uint16_t size )
{
    st_array this;
    this.array = malloc( size * sizeof( void* ) );

    this.push = &pushInArray;
    this.at = &getAt;
    ...
    return this;
 }

In order to use this, I need to cast for my variable type at each call of push() and at():

int item = 13;
st_array a = NewArray( 32 );
a.push( &a, (void*)&item );

int item_ret = *(int*)a.at( &a, 0 );

I'd prefer to avoid the casting (although it can be useful, just to keep the programmer aware of what is she doing). It is possible to define a function-style macro that accept the type as parameter, but I still have to figure out how to "bind" it to the structure function pointer.

I imagine the simplest solution might just be defining a function-style macro as (now written without checks to keep it short):

#define push( a, b ) a->array[a->used++] = ( void* )b
#define get( a, i, t ) *(t*)a->array[i]

and using them as:

push( &a, &item );
int item_ret = get( &a, 0, int );

but I'm not that sure on how to proceed. Can somebody please give me some advice?

  • 1
    `this.array = malloc( size * sizeof( void* ) );` and `this.state = malloc( size * sizeof( int ) );` make no sense considering the definition of `st_array`. – Quentin Jan 03 '17 at 16:36
  • 2
    "*I need to cast for my variable type*" in C you don't. Conversion to/from a `void`-pointer is done implicitly in C. So in your case for the pushing you can drop the cast. – alk Jan 03 '17 at 16:37
  • 1
    "*how to proceed.*" to where? – alk Jan 03 '17 at 16:39
  • 1
    @Quentin The `malloc` on `this.state` has been removed, you are right, it was from a previous implementation of `st_array`. Nonetheless, I didn't get why it is useless on `this.array` – Matteo Furlan Jan 03 '17 at 16:47
  • `NewArray()` function does not initialize the `.state` field. Rest of code does not use `.state`. What is the role of `.state`? – chux - Reinstate Monica Jan 03 '17 at 16:47
  • @alk Right, I don't need the `void` cast on the push side. Still needed on the `get` side. So *how to proceed* to avoiding that last cast? Or should I stick with the function-style macro? – Matteo Furlan Jan 03 '17 at 16:49
  • @chux Sorry, fixed `state` for `used` (that is was "used" but not declared) – Matteo Furlan Jan 03 '17 at 16:50
  • With `void* array;`, `this->array[this->used++] = item;` is undefined behavior (UB). – chux - Reinstate Monica Jan 03 '17 at 16:50
  • OK. `.used` is not assigned in `NewArray()`, that is problematic. – chux - Reinstate Monica Jan 03 '17 at 16:52
  • @chux since I `malloc`ed `this.array` in the "constructor" function, I though it could be valid (see [here](http://stackoverflow.com/questions/3536153/c-dynamically-growing-array) for example). Or is it UB only because of `void*`? – Matteo Furlan Jan 03 '17 at 16:55
  • @MatteoFurlan the `sizeof(void*)` should be replaced with the size of what you want to store. – Quentin Jan 03 '17 at 16:55
  • @chux yes, I left some `...` to cut it short, of course used must be initialized (to zero) – Matteo Furlan Jan 03 '17 at 16:56
  • "*avoiding that last cast*" using `void`-pointers implies loss of (type) info. So you *need* to store the info somewhere and *need* to cast once when retrieving the data "from" the `void`-pointer. Conclusion: You cannot avoid a cast. How to do it (the "back"-casting) can be considered an implementation detail. There a various ways to do this. – alk Jan 03 '17 at 16:56
  • "... C a dynamic array that can accept different types." Is that all types or select types from a predefined list? – chux - Reinstate Monica Jan 03 '17 at 16:58
  • @Quentin so basically I have to create a different array for every kind of data I need to store, I cannot use a single structure. Void pointers in C should "contain" by size any pointer to any type/data structure (not function, thou) so it's not very clear why I should replace `sizeof(void*)`... – Matteo Furlan Jan 03 '17 at 16:59
  • @alk Thank you, I'll dig through this @chux I though I could store a type as an `enum` type, and then use a `switch` to back-cast to the right type, but I liked the idea of not having to modity the `st_array` every time I needed a new type of array – Matteo Furlan Jan 03 '17 at 16:59
  • 1
    @MatteoFurlan That's because I misread your code! My bad. So you're allocating an array of `void*` that each point to an object. In that case the `malloc` has the correct size (even though `malloc(size * sizeof *array)` would be DRY-er), but `array` should be `void**`, not `void*`. – Quentin Jan 03 '17 at 17:02
  • "*but array should be `void**`, not `void*`*" essential, important comment. – alk Jan 03 '17 at 17:04
  • @Quentin, @alk Blimey, I wanted to store an array of pointers in a single pointer...of course I am so wrong. yes, It should be `void**` and it would not store at all the data. Sorry, you are right, (and I'm 10 hour into shift, so, yes, I am cooked). Now it is clear that I have to reroute everything. Thank you all! – Matteo Furlan Jan 03 '17 at 17:14

1 Answers1

0

Ok, I'm summing up an answer learning from all the constructive comments I was so lucky to gather up here:

The structure st_array as declared can only hold an array of pointers, given that the declaration is modified such that void * array becomes void ** array:

typedef struct st_array
{
     void** array;
     uint8_t size, used;
     ...
 } st_array

Hence there is no need to cast to void* when push()ing into the array but an explicit cast is of course needed when get()ing the required value out of it.

The simplest possible approach is defining a function-style macro

#define get( struct_ptr, index, type )    if(index <= struct_ptr->used) (type*)struct_ptr->array[index]; else NULL; (void)index

that returns the pointer to the required item so that a size check can be easily performed (the latest instruction is only used to force the use of ; at the end of the get() statement, for notation consistency).

Since it is not possible to point to a function-style macro (the code of the macro is replaced at compile time, so no actual function is defined), for consistency the push() function should be implemented without a pointer reference inside the structure.