This will not work. Why? Because A
has a fixed layout:
sizeof(other)
sizeof(MyParams)
sizeof(MyParams)
...
The things that can fit in MyParams
slots cannot be bigger than MyParams
. So, in general, the variable size B
won't fit there. You also have a problem of alignment: if you append items immediately after one another, you're ignoring the alignment requirements on your platform. So the simple appending without padding for alignment will not work.
You really seem to wish for A
to be a collection of objects of arbitrary size. In most general way, you can simply store an array of universal (void*
) pointers:
typedef struct {
Other other;
void ** params;
} A;
You can then toss "anything" into params
:
void test(void) {
A a;
a.params = (void**)malloc(sizeof(void*) * 3);
a.params[0] = malloc(sizeof(MyStruct));
((MyStruct*)a.params[0])->one = 1;
...
a.params[1] = malloc(sizeof(B) + arraysize);
((A*)a.params[1])->size = arraysize;
...
a.params[2] = NULL; // you have to somehow indicate the last item
}
To deallocate, iterate params
until you get a null element:
void destroyA(A * a) {
void ** p;
for (p = a->params; *p; ++p) free(*p);
free(a->params);
}
Alas, you have no indication what's really stored in each of the params
pointers - so it's hard to use them safely.
To be a bit more type safe, you should have a discriminated (tagged) union to represent the "universal" elements.
enum AnyTag { BTag = 0, MyParamsTag = 1 };
typedef struct {
...
} B;
typedef struct {
...
} MyParams;
typedef struct {
AnyTag tag;
B d;
} TaggedB;
typedef struct {
AnyTag tag;
MyParams d;
} TaggedMyParams;
typedef union {
AnyTag tag;
TaggedB b;
TaggedMyParams myParams;
} Any;
typedef struct {
Other other;
Any ** params;
} A;
Then it's much easier to ensure that you access the members correctly:
void destroyA(A * a) {
Any ** p;
for (p = a->params; *p; ++p) free(*p);
free(a->params);
}
void test(void) {
A a;
a.params = (Any**)malloc(sizeof(AnyTag*) * 3);
a.params[0] = (Any*)malloc(sizeof(TaggedMyParams));
a.params[0]->myParams.tag = MyParamsTag;
a.params[0]->myParams.d.one = 1;
...
a.params[1] = (Any*)malloc(sizeof(TaggedB) + arraysize);
a.params[1]->b.tag = BTag;
a.params[1]->b.d.size = arraysize;
memcpy(a.params[1]->b.d.values, input, arraysize);
...
a.params[2] = NULL;
...
process(&a);
}
void processMyParams(MyParams *);
void processB(B *);
void process(A * a) {
Any ** param = a->params;
for (Any ** param = a->params; *param; ++param) {
switch(param->tag) {
case MyParamsTag:
process(¶m->myParams.d);
break;
case BTag:
process(¶m->b.d);
break;
}
}
}
If you're willing to put the tags into the base types B
and MyParams
themselves, a trick shown here you can get rid of the d
so that things are easier to type:
enum AnyTag { BTag = 0, MyParamsTag = 1 };
typedef struct {
AnyTag tag;
...
} B;
typedef struct {
AnyTag tag;
...
} MyParams;
typedef union {
AnyTag tag;
B b;
MyParams myParams;
} Any;
typedef struct {
Other other;
Any ** params;
} A;
And:
void test(void) {
A a;
a.params = (Any**)malloc(sizeof(AnyTag*) * 3);
a.params[0] = (Any*)malloc(sizeof(TaggedMyParams));
a.params[0]->myParams.tag = MyParamsTag;
a.params[0]->myParams.one = 1;
...
a.params[1] = (Any*)malloc(sizeof(TaggedB) + arraysize);
a.params[1]->b.tag = BTag;
a.params[1]->b.size = arraysize;
memcpy(a.params[1]->b.values, input, arraysize);
...
a.params[2] = NULL;
...
process(&a);
}