tags:

views:

204

answers:

5

Is it possible to have an array of elements (structs) where the values vary in size?

The issue I am faced with is that I do not know how to access an element since it requires a cast. The "array" just contains pointers to the structs. Therefore I have decided to go with void**. As not each of the elements is of the same type, the type needs to be stored in the array as well so that I would know what to cast to without making rough guesses. This does not sound very efficient though. Is there not a more efficient way?

A: 

For this, we use an array of pointers to the actual objects.

The pointers are all the same size.

The objects to which they point can be different sizes.

A "discriminated union" works well for this.

typedef struct { ... } this_type;
typedef struct { ... } that_type;
typedef struct { 
     int subtype;
     union {
         this_type this;
         that_type that
     }
} discriminated_union;

An array pointers to discriminated_union works out well.

S.Lott
If I understood the OP correctly, the types of the objects differ.
Konrad Rudolph
Thanks for the instant answer.Yes, that is exactly what I have thought of. The problem is that I do not know how to cast the received element as the original type is not known.
The standard approach is to use a "discriminated union" of the various types.
S.Lott
A: 

Casts are not inefficient since they just tell the computer how to interpret the region of memory. When they are compiled to assembly, the compiler will create assembly that interprets the region of memory as needed.

rzrgenesys187
This does not hold for languages such as C# and Java, and that might lead to programmers used to these languages to avoid casts in C for performance reasons.
Jonatan
+10  A: 

No, in C each element of an array have to be of the same type (and thus the same size as well).

You might need to add another layer of abstraction, in this case you need to annotate the elements with the type it has e.g. you could make an array of structs that look like:

enum ElemType {
  TypeNull,
  TypeFoo,
  TypeBar
];

struct Elem {
 enum ElemType type;
 void *realElem;
};

You'd have to update the 'type' with the actual type you insert, and make decisions on that when you read the array, and you store a pointer to the actual element in the 'realElem' member.

struct Elem arr[42];
...
switch(arr[k].type) {
  case TypeFoo:
     handleFoo(arr[k].realElem);
     break;
  ...
}
nos
Thanks. I hoped to get around the saving of the type somehow but with the ElemType-enum it does not seem to be too much overhead though.
But as soon as you would use the object, you would have to know what type they are no? So you have to store that info, either using the compiler (which is not supported in C) or by meta data as above. What you could do though is to keep callbacks in the struct to interract with the data, for a more OO-approach.
Jonatan
+2  A: 

No, C arrays have a constant size that must be known at compiletime. But you can go around this by using pointers and dynamic allocation.

The problem here is not efficiency as much as as it is type safety. Casting has no runtime performance cost, it's only a mechanism for talking to the compiler, ask it to "trust you" and assume there's a certain type behind a sequence of bits. We're talking about safety because if you're wrong then anything at all can happen in runtime.

It is impossible in C (a static language) to construct an array of different types. What you can do is construct an array of a certain struct but have that struct hold pointers to objects of varying sizes. For instance, have the struct hold a char* pointer, in which case each object holds a string of one length or another and thus have a variable size (in terms of total memory used in runtime, not in terms of contiguous space used).

If you want each of these objects to behave differently then others then you can emulate polymorphism by using an array of function pointers and then add a function pointer to the struct that points to any of the functions as necessary.

wilhelmtell
That is an interesting idea. Did I get it right that the array only consists of function pointers? But what are the functions actually doing? Returning the value and the associated type? Hm, this would result in many function calls considering array sizes of 10,000+ items. However, the approach nos suggested seems to be more efficient.
+6  A: 

If you don't like casts, you can always used unions ( and a flag to indicate which type the union should be interpreted as )

#include <stdio.h>

typedef struct {
    int a;
} A;

typedef struct B {
    double b;
} B;

typedef struct C {
    char c[20];
} C;

typedef enum {
    TypeA,
    TypeB,
    TypeC,
} type;

typedef struct {
    type type;
    union { A*a; B*b; C*c; } p;
} TypedPointer ;

void foreach (TypedPointer* list, void (*fn)(TypedPointer))
{
    while (list->p.a) {
        fn(*list);
        ++list;
    }
}

void print_member (TypedPointer ptr)
{
    switch (ptr.type) {
        case TypeA: printf("A (%d)\n", ptr.p.a->a); break;
        case TypeB: printf("B (%f)\n", ptr.p.b->b); break;
        case TypeC: printf("C (%s)\n", ptr.p.c->c); break;
    }
}

int main ()
{
    A a = { .a = 42 };
    B b = { .b = 1.01 };
    C c = { .c = "Hello World!" };

    TypedPointer ptrs[] = {
        { .type = TypeA, .p.a = &a },
        { .type = TypeB, .p.b = &b },
        { .type = TypeC, .p.c = &c }, 
        { .type = 0, .p.a = 0} };

    foreach(ptrs, print_member);

    return 0;
}
Pete Kirkham
Thanks. I think I will go with your approach. It is the most cleanest and does not involve error-prone casting. One final question: Is there any reason why you are using pointers within the union? If I am correct, removing them would save four bytes (on x86 at least) per union-item. The only reason I can think of is your while-loop as one would need an additional counter-variable for iterating over the array.
Because in your OP, you said you had an a array of pointers to different structs. You can do it using a union of the structs directly, though that may also waste space as the union will be the size of the largest member.
Pete Kirkham