4

I'm in a position where I need to get some object oriented features working in C, in particular inheritance. Luckily there are some good references on stack overflow, notably this Semi-inheritance in C: How does this snippet work? and this Object-orientation in C. The the idea is to contain an instance of the base class within the derived class and typecast it, like so:

struct base {
    int x;
    int y;
};

struct derived {
    struct base super;
    int z;
};

struct derived d;
d.super.x = 1;
d.super.y = 2;
d.z = 3;
struct base b = (struct base *)&d;

This is great, but it becomes cumbersome with deep inheritance trees - I'll have chains of about 5-6 "classes" and I'd really rather not type derived.super.super.super.super.super.super all the time. What I was hoping was that I could typecast to a struct of the first n elements, like this:

struct base {
    int x;
    int y;
};

struct derived {
    int x;
    int y;
    int z;
};

struct derived d;
d.x = 1;
d.y = 2;
d.z = 3;
struct base b = (struct base *)&d;

I've tested this on the C compiler that comes with Visual Studio 2012 and it works, but I have no idea if the C standard actually guarantees it. Is there anyone that might know for sure if this is ok? I don't want to write mountains of code only to discover it's broken at such a fundamental level.

Community
  • 1
  • 1
agd
  • 99
  • 8
  • 1
    I think if you paid attention to the struct packing rules of the compiler (think of the boundaries of the parent classes) you could make it work. But it seems fragile and not very elegant or portable. And not easy to maintain. – Charlie Burns Sep 25 '13 at 01:09
  • Looks like the accepted answer for this problem http://stackoverflow.com/a/3766251/2770712 explains it pretty well. – Freddie Sep 25 '13 at 01:14
  • 1
    @Freddie, that's a good link. But it's a very simple case compared to this where the OP is talking about deep hierarchies and perhaps not uniform types. If all the fields are pointers or ints, then it's not a problem but as soon as you get into strangely packed and sized structs then "combining" them together is going to depend on the struct packing of the compiler. Just my 2 cents. – Charlie Burns Sep 25 '13 at 01:19
  • 1
    @CharlieBurns I completely agree with you. OP can use this method of the structs are guaranteed to be packed exactly the same. The packing will depend on the architecture of the system and the compiler being used. Again, as you stated, this isn't very portable. – Freddie Sep 25 '13 at 01:44
  • Thanks for the info guys. If this idea is compiler dependent at all I think I'll have to leave it to rest. – agd Oct 24 '13 at 21:11
  • `struct base b = (struct base *)&d;` yields `error: invalid initializer`. – David C. Rankin Sep 03 '16 at 01:55

3 Answers3

3

What you describe here is a construct that was fully portable and would have been essentially guaranteed to work by the design of the language, except that the authors of the Standard didn't think it was necessary to explicitly mandate that compilers support things that should obviously work. C89 specified the Common Initial Sequence rule for unions, rather than pointers to structures, because given:

struct s1 {int x; int y; ... other stuff... };
struct s2 {int x; int y; ... other stuff... };
union u { struct s1 v1; struct s2 v2; };

code which received a struct s1* to an outside object that was either a union u* or a malloc'ed object could legally cast it to a union u* if it was aligned for that type, and it could legally cast the resulting pointer to struct s2*, and the effect of using accessing either struct s1* or struct s2* would have to be the same as accessing the union via either the v1 or v2 member. Consequently, the only way for a compiler to make all of the indicated rules work would be to say that converting a pointer of one structure type into a pointer of another type and using that pointer to inspect members of the Common Initial Sequence would work.

Unfortunately, compiler writers have said that the CIS rule is only applicable in cases where the underlying object has a union type, notwithstanding the fact that such a thing represents a very rare usage case (compared with situations where the union type exists for the purpose of letting the compiler know that pointers to the structures should be treated interchangeably for purposes of inspecting the CIS), and further since it would be rare for code to receive a struct s1* or struct s2* that identifies an object within a union u, they think they should be allowed to ignore that possibility. Thus, even if the above declarations are visible, gcc will assume that a struct s1* will never be used to access members of the CIS from a struct s2*.

supercat
  • 77,689
  • 9
  • 166
  • 211
0

By using pointers you can always create references to base classes at any level in the hierarchy. And if you use some kind of description of the inheritance structure, you can generate both the "class definitions" and factory functions needed as a build step.

#include <stdio.h>
#include <stdlib.h>

struct foo_class {
  int a;
  int b;
};

struct bar_class {
  struct foo_class foo;
  struct foo_class* base;

  int c;
  int d;
};

struct gazonk_class {
  struct bar_class bar;
  struct bar_class* base;

  struct foo_class* Foo;

  int e;
  int f;
};

struct gazonk_class* gazonk_factory() {
  struct gazonk_class* new_instance = malloc(sizeof(struct gazonk_class));

  new_instance->bar.base = &new_instance->bar.foo;
  new_instance->base = &new_instance->bar;
  new_instance->Foo = &new_instance->bar.foo;

  return new_instance;
}

int main(int argc, char* argv[]) {
  struct gazonk_class* object = gazonk_factory();

  object->Foo->a = 1;
  object->Foo->b = 2;

  object->base->c = 3;
  object->base->d = 4;

  object->e = 5;
  object->f = 6;

  fprintf(stdout, "%d %d %d %d %d %d\n",
      object->base->base->a,
      object->base->base->b,
      object->base->c,
      object->base->d,
      object->e,
      object->f);

  return 0;
}

In this example you can either use base pointers to work your way back or directly reference a base class.

HonkyTonk
  • 1,961
  • 11
  • 11
-1

The address of a struct is the address of its first element, guaranteed.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243