Reputation: 101
I wrote the following code and it works, but I would like to know if a can be sure that it works all the time on all x86 machines.
#include <stdio.h>
#include <stdlib.h>
typedef struct Base {
int a;
float b;
} Base;
typedef struct Derived1 {
int a; // This two members have the same position as in the Base
float b;
// adding some other members to this struct
int otherMember;
int otherMember2;
} Derived1;
int main()
{
Base *bases[2];
// Filling the array with different structs
bases[0] = (Base*) malloc(sizeof(Base));
bases[1] = (Base*) malloc(sizeof(Derived1));
bases[1]->a = 5;
Derived1 *d1 = (Derived1*) bases[1];
if(d1->a == 5)
printf("SUCCESS\n");
return 0;
}
I know why this example works, but does it work always? Is there padding or similar stuff going on that can prevent this from working, or does the C standard even support this?
Upvotes: 2
Views: 112
Reputation: 60097
bases[1]->a = 5;
types the second block of the allocated memory with effective type Base
. When you then access it in if(d1->a == 5)
, through a pointer of type Derived
, you (arguably, though in accordance with how compilers generally interpret the rule) violate 6.5p7 and thereby make your program undefined.
You can make your code defined by using composition instead (make Base the first member (and first members have the same address as their host structs)), or, if you want uniform access to members from across a larger inheritance tree, you can use a strategy such as Typesafe Inheritance in C, which relies on unions.
Alternatively, there are compiler extensions that bring real slicing inheritance into C. In clang and gcc, these extensions can be enabled with -fms-extensions
or -fplan9-extensions
(the latter is a superset of the former with more features—see the documentation for more information).
Upvotes: 2
Reputation: 726809
According to C99 rules, these two struct
s are incompatible:
two structure, union, or enumerated types declared in separate translation units are compatible if their tags and members satisfy the following requirements: If one is declared with a tag, the other shall be declared with the same tag. If both are complete types, then the following additional requirements apply: there shall be a one-to-one correspondence between their members such that each pair of corresponding members are declared with compatible types, and such that if one member of a corresponding pair is declared with a name, the other member is declared with the same name. For two structures, corresponding members shall be declared in the same order.
Your code breaks one-to-one correspondence between members, so according to the standard this would be invalid:
Base *d1 = (Base*) bases[1];
d1->a=5; // Not valid
Fortunately, you can easily make it valid by embedding Base
into Derived1
:
typedef struct Derived1 {
Base base;
// adding some other members to this struct
int otherMember;
int otherMember2;
} Derived1;
According to C99,
A pointer to a structure object, suitably converted, points to its initial member
Hence, this is valid:
Base *d1 = (Base*) bases[1];
d1->a=5; // Valid
Note: This Q&A talks about a related topic of strict aliasing.
Upvotes: 5