Helios41
Helios41

Reputation: 31

Why is an int getting its value changed after casting in C

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

Answers (2)

5gon12eder
5gon12eder

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

P.P
P.P

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

Related Questions