74. Struct Magic

Die Wandlung zwischen zwei Objekten ist einfach ein Cast – aus GTK_BIN(foo) wandelt der Präprozessor in (GtkBin*)foo um. Warum funktioniert das?

Aus den gtk-Headern:

#ifdef GTK_NO_CHECK_CASTS
#define GTK_CHECK_CAST(tobj, cast_type, cast) \
  ((cast*) (tobj))
#else /* !GTK_NO_CHECK_CASTS */
#define GTK_CHECK_CAST(tobj, cast_type, cast) \
      ((cast*) gtk_type_check_object_cast \
      ((GtkTypeObject*) (tobj), (cast_type)))
#endif /* GTK_NO_CHECK_CASTS */

#define GTK_BIN(obj) \
  (GTK_CHECK_CAST ((obj), GTK_TYPE_BIN, GtkBin))

struct _GtkObject {
  GtkObjectClass *klass;
  guint32 flags;
  guint ref_count;...
}

struct _GtkWidget {
  GtkObject object;
  guint16 private_flags;...
}

struct _GtkContainer {
  GtkWidget widget;
  GtkWidget *focus_child;...
}

Was passiert hier? Zunächst erbt eine “Klasse” von der übergeordneten jeweils einfach durch “Einbetten” des structs der “Oberklasse” an erster Stelle in seinem eigenen struct, um danach die Felder zu definieren, die sie selbst braucht. Und das ist auch schon der ganze Trick, denn das Speicherlayout ist das gleiche, egal, ob der struct ausdefiniert ist oder nur eingebettet.

In einem einfacheren Beispiel:

struct _S1 {
  char b;
  int i;
}

struct _S2 {
  struct _S1 embedded;
  float extension;
}

...
struct _S1 s1; struct _S2 s2;
s1.b = 'c'; s1.i = 5;
s2.embedded.b = 'c'; s2.embedded.i = 5
s2.extension = 1e8;

Im Speicher sieht das dann etwa so aus:

(natürlich kann es auch irgendwie anders aussehen, der C-Standard schreibt die Details nicht vor, wohl aber, dass die Felder rechts und links gleich aufgeteilt sein müssen).

Weil im Speicher das Gleiche steht, können wir C beruhigt vorspiegeln, dass in s2 ein struct _S1 steht. Umgekehrt geht das aber nicht, weil ((struct _S2*)&s1)->extension auf Speicher zugreifen würde, der genau nicht zu s1 gehört.


Markus Demleitner

Copyright Notice