Reputation: 1319
I have some struct
s like this:
struct myStruct0 {
void(*deallocMe)(void*); // points always to "myStruct0_dealloc"
uint8_t* someStuff;
void* someOtherStuff;
}
void myStruct0_dealloc(void* structPtr) {
myStruct0* s = (myStruct0*)structPtr;
free(s->someStuff);
free(s->someOtherStuff);
free(s);
}
struct myStruct1 {
void(*deallocMe)(void*); // points always to "myStruct1_dealloc"
uint8_t* someStuff;
uint64_t largeArray[4096];
void* someOtherStuff;
char* someRealOtherStuff;
}
void myStruct1_dealloc(void* structPtr) {
myStruct1* s = (myStruct1*)structPtr;
free(s->someStuff);
free(s->someOtherStuff);
free(s->someRealOtherStuff);
free(s);
}
Both struct
s and the pointers are allocated using malloc (and of course must not be NULL).
Is the following code safe?
struct genericForm {
void(*deallocMe)(void*);
}
void deallocMyStructX(void* myStructX) {
genericForm* x = (genericForm*)myStructX;
x->deallocMe(myStructX);
}
Why do I want to do this?
I'll have an array of pointers to some struct
s like the above and if this would work I have not to save the deallocator for each struct
/pointer. This would really simplify my code.
Edit:
The structs can be completely different. The only thing they share is that they contain a function-pointer of the form void(*)(void*)
as the first element.
Upvotes: 1
Views: 1100
Reputation: 76405
If all you want is to access the first member of your structs, you should be fine. The offset of the first member is guaranteed to be 0, and if the first member is compatible across all structs (ie a function pointer), you should be allright. If you're going to dereference your structs and access other members, than you might run into problems due to padding. Adding placeholder members might solve that, but is arguably a tad inefficient.
If you don't have too many structs to worry about, a might be an easier solution here:
struct _data {
int identifier;
union {
struct type_1 {} _t1;
struct type_2 {} _t2;
struct _generic {
void (*dealloc_generic)(void *);//first member is dealloc function pointer
} _gen;
};
};
void dealloc_struct(struct _data * s)
{
switch (s->identifier)
{
case 1:
s->t1->delloc_t1_ptr(s->t1);
return;
//and so on
default:
//for generic-compatible structs
s->_gen->delloc_generic(s->_gen);
}
}
If the first member of your structs will always be a valid function pointer, then you could even write this:
void dealloc_struct(void *s)
{
//cast s to function pointer, call it and pass the pointer as argument
((void (*)(void *))s)(s);
}
This should work, according to the standard (C1x §6.7.2.1.13):
A pointer to a structure object, suitably converted, points to its initial member [...] and vice versa. There may be unnamed padding within as structure object, but not at its beginning.
Upvotes: 6
Reputation: 153367
Is casting between structs with different size allowed/safe in C?
In general: no. The addressing space of one structure may be entirely incompatible with another.
A solution is to put both structures in a union
.
struct myStruct0 {
void(*deallocMe)(void*); //Common elements must be in the beginning with same type/order
...
}
struct myStruct1 {
void(*deallocMe)(void*);
...
}
union genericForm {
struct myStruct0 s0;
struct myStruct1 s1;
}
A pedantic method would used
struct myStruct0 {
...
};
struct myStruct1 {
...
};
struct genericForm {
void(*deallocMe)(void*); //Common elements here
union { // Anonymous union
struct myStruct0 s0;
struct myStruct1 s1;
};
};
Upvotes: 2