Question

Is it possible to replicate an generic array in pure ANSI-C?

I have this struct which holds an array (for floats at the moment) and some variables like size and capacity for mutation in the array.

typedef struct _CustomArray
{
    float* array; //the array in which the objects will be stored
    int size; //the current size of the array
    int capacity; //the max capacity of the array
} CustomArray; 

I use this struct so I can make an array in pure C where I can add/remove items, dynamically expand the array size when needed etc. all the things a "standard" array does, except it is made in C only. And now I want to make this so that when you initialize this struct you can set the datatype of the elements it should hold, at this moment it's only capable of storing float datatypes, but I want to make it so that it can store any datatype/other structs. But I don't know if this is even possible.

At this moment the function to make this array is:

CustomArray* CustomArray_Create(int initCapacity, /*type elementType*/)
{
    CustomArray* customArray_ptr; //create pointer to point at the structure
    float* internalArray = (float*)malloc(sizeof(float) * initCapacity); //create the internal array that holds the items
    if(internalArray != NULL)
    {
        CustomArray customArray = { internalArray, 0, initCapacity }; //make the struct with the data
        customArray_ptr = &customArray; //get the adress of the structure and assign it to the pointer
        return customArray_ptr; //return the pointer
    }
    return NULL;
}

Is it possible to give a datatype as parameter so I can malloc memory for that datatype and cast it as that given datatype in an array dynamically?

Thanks in advance,

Marnix van Rijswijk

Was it helpful?

Solution

Your code has a serious problem... you're returning the address of a local variable (CustomArray) and when the function returns that variable is destroyed so you cannot keep using it with the pointer. You have to malloc also that structure so that the memory will be still available once the function returns.

About making the type a parameter you can get somewhat close using macros... for example with something like:

#include <stdlib.h> 
#define DefArray(type) \
typedef struct T_##type##Array {\
    type *array; \
    int size, capacity; \
} type##Array; \
static type##Array *type##ArrayCreate(int capacity)\
{\
    type##Array *s = malloc(sizeof(type##Array));\
    if (!s) return NULL;\
    s->array = malloc(sizeof(type) * capacity);\
    if (!s->array) { free(s); return NULL; }\
    s->size=0; s->capacity = capacity;\
    return s;\
}

Then you can use it this way

#include "customarray.h"
DefArray(float);
DefArray(double);

void foo()
{
    floatArray *fa = floatArrayCreate(100);
    ...
}

Note that you've to use macros to define all your custom functions. Note also that this approach will duplicate the code in each module (I'd say not a big issue but if you can't use C++ probably your target platform is pretty small). With a slightly more complex approach you could generate separate .h file and .c files for the implementation.

OTHER TIPS

Boy, this really sounds like a job for C++.

I think the closest you could come to this in C is to not pass the type, but rather the size (sizeof(type)).

You could make your function more generic so that it can do what it needs to do if all it knows is the size of each item in the array. This is how functions like bsearch() work.

One way of achieving this is to use so-called X-macros.

Here is a (probably buggy) generic vector implementation using this technique.

It is then used as

// defining generic parameters
#define PREFIX tv
#define ITEM token
#define NAME token_vector
#include "vector.h"

...
token_vector tv = tv_new(100);
*(tv.front) = some_token;
tv_push_back(&tv, other_token);

I messed around with generic programming in C a few years ago, just for the heck of it.

Basically, I ended up exploiting the preprocessor. I guess I was mildly successfull: I did accomplish some macro notation for several of the most important generic data structures.

What I definitely DIDN'T accomplish (in any automatic way at least) was recursively running the macros - i.e., creating an array-of-arrays or array-of-hashes etc. That's due to the interesting coughcrazycough semantics of C preprocessor macros.

If you're interested, here's the code: https://github.com/christianfriedl/CGenerics/blob/master/src/cgArray.h

So, there's this notion of an "effective type" for objects with no declared type of their own. (as it shakes out, those pretty much consist only of "the other end of an *alloc pointer" and a couple of weird union rules) Basically, the "effective type" of such an object is whatever you last used to assign to it, not counting times that was char or char[] because reasons.

One interesting interaction there has to do with the rules for declaring structure types. Namely, that you can be freely re-declare the same tag name (or lack of tag name), and each declaration introduces a totally new type (past names are shadowed out, but objects with the old types don't get reinterpreted).

So you can do something like this:

# define DECL_VECTOR(NAME,TYPE,SIZE) PUN_STRUCT(NAME,TYPE,SIZE)  INIT_STRUCT(NAME,TYPE,SIZE) 

# define PUN_SIZE sizeof(void*)+sizeof(int)*2


# define PUN_STRUCT(NAME,TYPE,SIZE)                      \
   struct {                                              \
      TYPE (*p)[(SIZE)];                                 \
      int size;                                          \
      int capacity;                                      \
   } *NAME = malloc(PUN_SIZE);                        


# define INIT_STRUCT(NAME,TYPE,SIZE)  do {               \
   if (!NAME) {                                          \
        perror("malloc fail");                           \
        abort();                                         \
   }else {                                               \
        NAME->size = (SIZE);                             \
        NAME->capacity = 0;                              \
        NAME->p = malloc(sizeof(*NAME->p));              \
        if (!NAME->p) {                                  \
            perror("malloc fail");                       \
            abort();                                     \
        }                                                \
        NAME->p = (TYPE(*)[(SIZE)])(NAME->p);            \
   }                                                     \
   }while(false)


int main(int argc, char *argv[]) 
 {

   DECL_VECTOR(vec1,int,8);


    printf("size of handle struct:  %zu,\n\
size of vector array:   %zu,\n\
size of vector element: %zu\n\
address of handle struct: %p,\n\
address of vector array:  %p\n",  sizeof(*vec1),       \
                                  sizeof(*vec1->p),    \
                                  sizeof(*vec1->p[0]), \
                                  vec1,                \
                                  vec1->p);
   free(vec1->p);
   free(vec1);
   return 0;
 }

(however, people may accuse you of abusing your macro privileges, and they may not be entirely wrong)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top