Wir können also in unserem Beispiel die Eltern eines Menschen nicht direkt als Menschen, sondern als Verweise auf Menschen deklarieren, was so aussieht:

struct Human { char* name; Human* mother; Human* father; };

Die Größe einer Human-Variable lässt sich nun eindeutig bestimmen: 4 Byte werden für den Namens-Verweis benötigt und noch einmal je 4 Byte für die Verweise auf die beiden Eltern; macht 12 Byte beziehungsweise 3 Speicherzellen insgesamt. Legen wir nun also eine Variable vom Typ Human wie folgt an

void h() { Human h; ... }

dann ergibt sich das folgende Speicherbild:

Adresse Inhalt Inhalt Inhalt Inhalt Var
123450 <– esp
123454 <– h
<– h.name
h
123458 <– h.mother
123462 <– h.father
123466 <– ebp
123490 <– bottom

Um nun einem Menschen einen Elternteil zuzuweisen, nutzen wir das aus dem vorletzten Artikel bekannte new-Schlüsselwort:

void h() { Human h; h.mother = new Human; }

Hier passiert nichts anderes, dass als durch new Human auf dem Heap der benötigte Speicherplatz für einen neuen Menschen reserviert wird und die Start-Adresse dieses reservierten Speicherbereiches an h.mother zugewiesen wird. Der Ausdruck h.mother verweist also im folgenden auf den gerade reservierten neuen Menschen.

Wollen wir dagegen ausdrücken, dass ein Mensch keine Eltern hat, weisen wir den Variablen einen Nullpointer zu. Dafür gibt es in C++ verschiedene Möglichkeiten; entweder nutzt man direkt die Zahl 0, oder – ab dem neuen Standard C++11 – das spezielle Schlüsselwort nullptr (beides drückt das gleiche aus):

void h() { Human h; h.mother = 0; h.father = nullptr; }

Jetzt stellt sich natürlich die Frage, wie man mit den Eltern eines Menschen weiterarbeiten kann – experimentierfreudige werden merken, dass etwa der Ausdruck h.mother.mother zu einer Fehlermeldung seitens des Compilers führt. Im nächsten Artikel werden wir demnach sehen, wie man mit Pointern arbeiten kann. Seid gespannt!

1 / 2 / 3

Kommentare (2)

  1. #1 Nicolai
    November 13, 2013

    0 und nullptr drücken nicht das selbe aus – das eine kann als integraler Wert verwendet werden, das andere nur als Zeiger-Adresse und zur true / false Abfrage. Im Gegensatz zu NULL, das meistens als #define NULL 0 implementiert ist, ist der nullptr somit nur für Zeiger-Operationen gültig.

    Danke für die ausführlichen Artikel, ich finde es klasse, dass du ein wenig auf die Technik im Speicher eingehst. 🙂

    Kleine Frage: Kannst du grob erklären, wie ein Konstruktor übersetzt wird? Ich würde es mir wie eine normale Klassenfunktion vorstellen, Aufruf mit dem this-Pointer und den Argumenten, weiß aber nicht, ob das korrekt ist..

  2. #2 Marcus Frenkel
    November 13, 2013

    @Nicolai

    0 und nullptr drücken nicht das selbe aus – das eine kann als integraler Wert verwendet werden, das andere nur als Zeiger-Adresse und zur true / false Abfrage.

    Ich dachte mir schon, dass ein Kommentar dazu kommt. 😉
    Das gleiche sind sie natürlich nicht, aber sie drücken in Bezug auf die Nullpointer-Adresse das gleiche aus. Natürlich bestehen viele Unterschiede zwischen beiden Werten, aber im Kontext dieser einfachen Zuweisung sind sie äquivalent.

    Konstruktor kommt demnächst. So ein bisschen sind das wirklich normale Klassenfunktionen, die aber ein bisschen Extramagie beim Aufruf betreiben. Insbesondere werden hierbei zu Beginn des Konstruktors der Konstruktor der Basisklasse sowie die Konstruktoren alle Klassen-Attribute aufgerufen. Wo im Falle von RTTI die Typinformation gesetzt wird, weiß ich gerade gar nicht, das könnte aber auch im Konstruktor sein.