4

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

Marnix v. R.
  • 1,638
  • 4
  • 22
  • 33
  • Don't think in Pure C you can pass data types that way. Looking into languages that support heterogeneous array list, e.g C#, it only works for non basic data types, that is Classes and not on int,float etc. Since C isn't object oriented, it's highly unlikely that you would get this facility. – Shamim Hafiz - MSFT Dec 12 '10 at 17:57
  • 2
    don't start identifiers with underscores: such names are reserved for the implementation (compiler+libc); using an underscore and an upper-case latter is doubly-bad: these names are reserved in any context because it's what new language features use (eg `_Pragma`, `_Complex`, `_Bool`, ...); an easy workaround is to use trailing underscores, which also plays nice with prefix-based namespacing – Christoph Dec 12 '10 at 18:06
  • 1
    There are a number of question on the site concerning how one can construct object oriented behaviors in c: [Object-Orientation in C](http://stackoverflow.com/q/415452/2509) and [Can you write object oriented code in C?](http://stackoverflow.com/q/351733/2509) and others. You could accomplish your desired outcome with a judicious use of `sizeof` and the function pointer mechanism discussed in the links, but it will be more work that it is worth. The interface of `qsort` and `bsearch` is a compromise. – dmckee --- ex-moderator kitten Dec 12 '10 at 18:10
  • 2
    Trying to make C into a different language is a misguided pursuit. As soon as your start writing your own highlevel types on top of C and writing all your C code using those types, you might as well use a higher level language to begin with. In fact you're a lot worse off, because the fancy types in C++, ocaml, etc. were written by experts and probably perform well and don't have bugs... If you want to write C, use it for what it is and take advantage of what it is to make your code efficient. – R.. GitHub STOP HELPING ICE Dec 12 '10 at 18:14

5 Answers5

8

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.

6502
  • 112,025
  • 15
  • 165
  • 265
  • it works, and it also showed me a complete new way of doing things. awesome. – Marnix v. R. Dec 12 '10 at 19:31
  • 3
    Hehe... welcome to the world of metaprogramming (writing code that writes code). C preprocessor is an horribly weak form of metaprogramming, C++ template machinery is just a little bit better. For the real magic you've to use either external generators (writing C/C++ generators for example in python/perl is easy) or move to other languages where serious metaprogramming is available (e.g. Lisp). – 6502 Dec 12 '10 at 21:04
3

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.

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • hmm thanks, i know its more a job for c++. but i was wondering if it was possible. :[ sadly it's not. – Marnix v. R. Dec 12 '10 at 17:59
  • Is it safe to make assumptions on data type just by looking at the size? – Shamim Hafiz - MSFT Dec 12 '10 at 17:59
  • Gunner: It's safe to make assumptions about how much memory each item uses. That should be enough for allocating and moving memory. The only problem is if the data type is a pointer. In that case, the item pointed to should also be copied. But for all basic types, this approach is perfectly valid. – Jonathan Wood Dec 12 '10 at 18:02
  • Marnix: I really don't see this as a problem. As long as your base data type does not involve pointers, the method I described will work just fine. So, instead of your function calling malloc(sizeof(float) * initCapacity), you would call malloc(size * initCapacity). If that works, please mark my response as the answer. – Jonathan Wood Dec 12 '10 at 18:05
  • thanks for the tip, i will try it this way and mark your response if it works. – Marnix v. R. Dec 12 '10 at 18:08
2

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);
elder_george
  • 7,849
  • 24
  • 31
0

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

Bet Lamed
  • 473
  • 3
  • 15
0

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)

l.k
  • 199
  • 8