Jonas Juffinger
Jonas Juffinger

Reputation: 101

"Dynamic Inheritance" in C

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

Answers (2)

Petr Skocik
Petr Skocik

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

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726809

According to C99 rules, these two structs 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

Related Questions