views:

298

answers:

5

I'm struggling to create a generic (or untyped) array in C (I'm aware that C++ makes this easier). In a nutshell I want to allocate an array to hold an array of a specific known type (at runtime). In the real implementation it depends on user input.

I've been trying to use a enum/struct scenario following the advice found in several Google hits but I'm afraid that my inexperience with void pointers and lack of a concrete example are preventing me from getting a working piece of code. (Normally I would just buy a book but I'm in a country where I don't speak the language and English programming books are non-existent.)

The problem boils down to the following simplification: I have an image (only 1D) were the pixel values may be int, float, or double. I do have a function that will tell me the type. All I want to do is store the pixels in an array of the appropriate type. (In practice these images are very large and my motivation is to save memory and to prevent writing blocks of code for each type.)

I've been trying something like the following but maybe it's not the best anyway (code snippet for a possible data structure) :

enum type {
    typeint, typefloat, typedouble
};

struct genericarray {
    enum type type;
    void *storage;
};

Somehow I want to store those pixels in instances of generciarray. All my attempts so far have turned into "warning: dereferencing ‘void *’ pointer" parties, which I admit I am not understanding.

I'm be eternally grateful for any help and in particular for some simplified example how to get a working generic array. The example could just use for-loops to init. I can figure out how to load my actual images.

+2  A: 
enum type {
    typeint, typefloat, typedouble
}

struct genericarray {
    enum type type;
    union { int i; float f; double d; } storage;
};

is what you need (imho.)

EDIT: to use void* as a container for int/float/double, you must allocate it: (array is genericarray of your old type - with void* storage)

array.storage = malloc(sizeof(int));

and dereference as:

(*((int*)array.storage))

replace float for the case of type == typefloat, etc.

and you must also free it

free(array.storage);
Yossarian
You cannot assign a pointer to array.storage. This code makes no sense.
jmucchiello
sorry.. i didnt wrote, that array is 'genericarray' of Dr. Person Person II's defined type.
Yossarian
+2  A: 

That's because you can't dereference a void pointer without first casting it to something else. This is because the C implementation needs to know what type of object the dereferenced pointer is.

In addition to the union suggested already, you can use the method you allude to, but you would have to conditionally convert (implicitly or explicitly with a cast) if you want to dereference storage:

int *iptr;
double *dptr; 

switch (x.type)
{
    case typedouble:
        dptr = x.storage; // implcit conversion example
        // reference your "image" as dptr[i] now
        break;
    case typeint:
        iptr = (int *)x.storage; // explicit conversion, actually unnecessary
        // reference your "image" as iptr[i] now
        break;
 }
Chris Young
I have done something very similar to this solution with success. I think this implementation has more code but is very obvious and intuitive how you are accessing the void * data.
Trevor Boyd Smith
A: 

You want to define a struct that has a an item that is a union of the three types you will need to use and then dynamically allocate a buffer of that type.

Thomas
+2  A: 

You don't actually specify the lines that gives the error but I imagine they might look a bit like

struct genericarray ga;
float fValue;

fValue = ga.storage[idx];

What you could do is add a cast

fValue = ((float*)ga.storage)[idx];

I'd recommend creating some macros or functions for setting and getting values

#define GET_STORAGE(_type, _src, _idx) \
  ((_type*)_src.storage)[_idx]
#define SET_STORAGE(_type, dst, _idx, _src) \
  ((_type*)_dst.storage)[_idx] = _src 

fValue = GET_STORAGE(float, ga, 3);

SET_STORAGE( float, ga, 3, sin(ang) );

I see 'Chris Young' using switch statements in his answers and that is a good thing for is these access functions.

epatel
+3  A: 

I would encapsulate the whole thing as follows:

#include "stdlib.h"
#include "stdio.h"

// structure
enum type { typeint, typefloat, typedouble };

struct genericarray
{
    enum type type;
    void ** storage;
};

typedef struct genericarray genericarray;

// allocate
void allocate(long numItems, enum type varType, genericarray * array)
{
    (*array).type = varType;
    switch (varType)
    {
        case typeint:
            (*array).storage = malloc(numItems*sizeof(int));
            break;
        case typefloat:
            (*array).storage = malloc(numItems*sizeof(float));
            break;
        case typedouble:
            (*array).storage = malloc(numItems*sizeof(double));
    }
}

// release
void release(genericarray array)
{
    free(array.storage);
}

// usage
int main(int argCount, char ** argList)
{
    genericarray image_1;
    genericarray image_2;

    int iv;
    float fv;

    allocate(10, typeint, &image_1);
    allocate(10, typefloat, &image_2);

    ((int *)(image_1.storage))[5] = 42;
    iv = ((int *)(image_1.storage))[5];
    printf("image_1[5] = %d\n", iv);

    ((float *)(image_2.storage))[5] = 3.14159;
    fv = ((float *)(image_2.storage))[5];
    printf("image_2[5] = %f\n", fv);

    release(image_2);
    release(image_1);

    return 0;
}
e.James
This is pretty much exactly what I've been after. Very nicely coded and instructional (especially nice because this is new territory for me). I want to study what you've wrote tonight before trying to generalize for my real code. So far yours is the best among a set of good answers. Thank you.
Dr. Person Person II
You are welcome. Thank you for the compliment, and good luck with the coding!
e.James
Rather than using an enum there, I would probably add an unsigned char elementsize property there instead.This would remove the neccessity for hte switch on varType, and makes it trivial to add new types.
Arafangion