Reputation: 31
I'm trying to cast a struct to access it's first member, but the values of the first member of the struct are getting changed/messed up.
Heres the code:
typedef struct ObjectBase
{
int integer1;
int integer2;
}ObjectBase;
typedef struct ObjectExtended
{
ObjectBase* baseObj;
char* string;
}ObjectExtended;
int main(int argc,char** argv)
{
void* voidObject = malloc(sizeof(ObjectExtended));
ObjectExtended* objExtended = voidObject;
objExtended->string = "TEST_OBJECT";
objExtended->baseObj = malloc(sizeof(ObjectBase));
objExtended->baseObj->integer1 = 10;
objExtended->baseObj->integer2 = 11;
printf("Extended Object:\n");
printf("\tString: %s\n",objExtended->string);
printf("\tInt1: %i\n",objExtended->baseObj->integer1);
printf("\tInt2: %i\n",objExtended->baseObj->integer2);
ObjectBase* objBase = voidObject;
printf("Base Object:\n");
printf("\tInt1: %i\n",objBase->integer1);
printf("\tInt2: %i\n",objBase->integer2);
free(objExtended->baseObj);
free(objExtended);
return 0;
}
Heres the output:
Extended Object:
String: TEST_OBJECT
Int1: 10
Int2: 11
Base Object:
Int1: 166544
Int2: 6
Wheres it getting the 166544 & 6 from?
Upvotes: 1
Views: 205
Reputation: 25419
You are using “virtual inheritance” so the ObjectExtended
does not contain an ObjectBase
as its first member but rather a pointer to one. As you cast an ObjectExtended *
to an ObjectBase *
, you start interpreting the high and low order bytes of that pointer as the integers of the ObjectBase
(assuming sizeof(int *) == 2 * sizeof(int)
).
To fix that problem, either use “non-virtual inheritance”, ie declare ObjectExtended
as
typedef struct ObjectExtended
{
ObjectBase baseObj; /* not a pointer */
char* string;
} ObjectExtended;
which would have probably been the better design in the first place anyway, or change your casting logic:
#define UPCAST(EXTPTR) ((EXTPTR)->baseObj)
Unlike a dynamic_cast
in C++ (which you eventually had in mind while writing your code), casting a pointer in C simply instructs the compiler to re-interpret whatever memory location the pointer points to as an object of the new type. If the first member of your struct
is the base object, this is fine, because the base object and the extended object both start at the same address. But in your original code, you'll need more than a reinterpretation of the pointer's destination. The address of the base object is not that of the extended one but different. Namely, it is the one stored in the baseObj
pointer. You can see that they cannot have the same address as you have created them using two different malloc
calls.
To make things more clear, here is the memory layout of your objects:
+----------------+<--+ + +
| integer1 | | | sizeof (int) | sizeof
+----------------+ | + | (ObjectBase)
| integer2 | | | sizeof (int) |
+----------------+ | + +
|
+----------------+ | + +
| baseObj |---+ | sizeof (ObjectBase *) | sizeof
| | | | (ObjectExtended)
+----------------+ + |
| string |---+ | sizeof (char *) |
| | | | |
+----------------+ | + +
V
somewhere else ...
The gap between the two objects is of course not to scale. Two calls of malloc
may return any addresses in any order.*
Had ObjectExtended
instead been declared as above (“non-virtual”), you'd have gotten the following layout:
+----------------+ + + +
| integer1 | | sizeof (int) | | sizeof
+----------------+ + | | (ObjectBase)
| integer2 | | sizeof (int) | |
+----------------+ + | +
| string |---+ | sizeof (char *) | sizeof
| | | | | (ObjectExtended)
+----------------+ | + +
V
somewhere else ...
In both pictures, the compiler might have added additional padding bytes to the objects to make them align in memory as required by the target architecture.
* And this is in contrast to a language like C++ with built-in support for inheritance where using virtual inheritance does not cause a dynamic memory allocation for the sub-object. Instead, a C++ compiler would have computed the sum of both memory requirements and then either reserve enough stack-space to accommodate for the entire object or allocate the required amount of memory on the free store (if the object were created using operator new
). You could have done the same in C by using malloc(sizeof(ObjectExtended) + sizeof(ObjectBase))
and then figure out the memory layout yourself. But you'd have to be more careful not to violate alignment rules.
Upvotes: 4
Reputation: 121397
ObjectBase* objBase = voidObject;
Here you are assigning a void pointer, which is a pointer struct ObjectExtended
. So with the above assignment, you are trying to assign one struct with another which has completely different memory layout and are incompatible. Hence, you get the weird output.
Since you are using void
pointer, compiler can't help you here as void
pointers are compatible with any type object pointer.
Either avoid using void
pointer as it's pretty much the root cause of this confusion, I think (although correct usage void
pointer is perfectly fine).
ObjectExtended * objExtended = malloc(sizeof(ObjectExtended));
//ObjectExtended* objExtended = voidObject; This is not needed anymore.
and when you later want to access the member, use the correct member to assign:
ObjectBase* objBase = objExtended->baseObj;
This usage would have given you warning such as:
warning: initialization from incompatible pointer type
OR
If you want to stick with void pointer, then use the correct object type by casting it:
ObjectBase* objBase = ((struct ObjectExtended*) voidObject)->baseObj;
Upvotes: 1