Advanced  Services Autopointer Back Next Up Home


Data, Stack, Heap

Für die Variablen und Konstanten stehen einem Programm drei Bereiche zur Verfügung, Data, Stack und Heap. Im Data-Speicher werden die vereinbarten Konstanten abgelegt, im Stack die definierten Variablen. Diese beiden Bereiche verwaltet die Anwendung selbst. Belegt man Speicher auf dem Stack, so ist man auf der sicheren Seite, denn dieser wird automatisch freigegeben, wenn die Variable nicht mehr gebraucht wird.

Beispiel für Daten, die in der Data-Section abgelegt werden.

void data()
{
   const int cint = 17;   // Konstante wird angelegt in der Data-Section
   const char cchar = 'a';   // Konstante wird angelegt in der Data-Section
   const char  *st = "foo";  // deprecated conversion from string constant to 'char*'
   // Stringkonstante wird in der Data-Section angelegt (meistens...)
   // Zeiger selbst auf dem Stack.
   st = "bar";
}
// Stackspeicher wird nach dem Ende der Funktion freigegeben

Beispiel für Variablen, die im Stack abgelegt werden.

void stack()
{
   int a=0;   // Stack
   bool boo = true; // Stack
   int *ipoi;  // Pointervariable wird auf dem Stack angelegt (nicht initialisiert)
   int iArr[17];
   // iArr ist ein const-Pointer auf den Anfang eines Speicherblocks der Größe 17*sizeof(int)
   // Pointer und Speicherblock auf dem Stack
   char chArr[] = { 'a', 'b', 'b' };
   // chArr ist ein const-Pointer auf den Anfang eines Speicherblocks der Größe 3*sizeof(char)
   // Pointer und Speicherblock auf dem Stack
   double dou = 12.34;    // Stack
   double *dpoi = &dou;   // Stack
}

Beispiel für Variablen, die auf dem Heap abgelegt werden.

void stackandheap()
{
   char *chpoi = new char[17];
   // (Initialisierter) Pointer wird auf dem Stack angelegt
   // chpoi ist ein Pointer auf den Anfang eines Speicherblocks der Größe 17*sizeof(char) auf dem Heap
   int *ipoi = new int[17];
   // Initialisierter Pointer, Pointer selbst wird auf dem Stack angelegt
   // ipoi ist ein Pointer auf den Anfang eines Speicherblocks der Größe 17*sizeof(int) auf dem Heap
   delete [] ipoi;  // Speicher wird feigegeben
   delete [] chpoi;  // Speicher wird feigegeben
}

Memory Leaks

Dynamischer Speicher dagegen, der zur Laufzeit angefordert wird, wird auf dem Heap angelegt und muß vom Entwickler selbst freigegeben werden. Auch erfahrene Entwicklern können das in komplexen Situationen übersehen. Im Prinzip passiert dann folgendes.

void memoryleak()
{
   char *chpoi = new char[17];
   // (Initialisierter) Pointer wird auf dem Stack angelegt
   // chpoi ist ein Pointer auf den Anfang eines Speicherblocks der Größe 17*sizeof(char) auf dem Heap
   int *ipoi = new int[17];
   // Initialisierter Pointer, Pointer selbst wird auf dem Stack angelegt
   // ipoi ist ein Pointer auf den Anfang eines Speicherblocks der Größe 17*sizeof(int) auf dem Heap
   // delete [] ipoi;  // Speicher wird feigegeben
}
// delete [] chpoi; wurde vergessen, keine Möglichkeit das noch nachzuholen, da chpoi nicht mehr existiert.

Im obigen Beispiel ist leicht zu sehen, daß der Speicher, auf den chpoi zeigt, nicht freigegeben wurde, in Projekten in denen mehrere Entwickler arbeiten, ist das nicht so leicht zu sehen.

Das bisher gesagt gilt natürlich genauso für komplexe Variablen und Konstanten, also für Objekte.


Autopointer

Hier schauen C++-Entwickler etwas neidisch auf die Java- und C#-Kollegen, denen eine Laufzeitumgebung zur Verfügung steht, die den Speicher aufräumt. Aber in der Weiterentwicklung von C++ gibt es mittlerweile ähnliche Konzepte, besonders im neuen C++11 Standard. Ein erster Versuch in diese Richtung waren Autopointer. Sie sind zwar mit der Version C++11 deprecated, sollen aber trotzdem kurz besprochen werden. Aus den Nachteilen, die Autopointer haben, wird dann ersichtlich warum es seit C++11 neuere und bessere Konzepte gibt.

Ein Autopointer ist ein Objekt einer Klassenschablone, das man ähnlich wie einen normalen Pointer verwendet und das den angeforderten Heapspeicher selbst freigibt und auf diese Weise Memoryleaks verhindert. Für Autopointer braucht man das Include memory

Die folgenden Beispiel verwenden die einfache Klasse Test, die im Destruktor eine Konsolmelduing ausgibt. Auf diese Weise kann man die Speicherfreigabe durch den Autopointer einfach verfolgen.

class Test
{
public:
   int i;

public:
   Test(int j = 0) : i(j) {}

   ~Test() { cout << "destructor test" << endl;  }
};

Das folgende Beispiel zeigt dsie prinzipielle Verwendung von Autopointern. In den spitzen Klammern wird der aktuelle Datentyp angegeben. ap(new Test(5)) ist der Konstruktoraufruf für die Instanz ap, der ein Zeiger auf ein Objekt vom Typ Test übergeben wird.

void autopointer1()
{
   std::auto_ptr<Test> ap(new Test(5));
   cout << p->i << endl;
}

Der Ablauf:

autopointer_01.jpg


Die Memberfunktion get()

Autopointer verfügt über eine Memberfunktion get(), die den originalen Pointer liefert: Die Operatoren ++ und << sind nicht überladen, * dagegen schon.

void autopointer2()
{
  std::auto_ptr<int> ap (new int);
  //std::cout << ap << '\n';   // << nicht überladen
  cout << "Speicheradresse: " << ap.get() << '\n';   // Speicheradresse
  cout << "Wert: " << *ap << '\n';  // * überladen
  *ap.get()=17;
  cout <<  "Wert: " << *ap << '\n';  //
  *ap = 18;
  cout <<  "Wert: " << *ap << '\n';  //
  (*ap)++;
  cout <<  "Wert: " << *ap << '\n';  //
  //ap++; // ++ nicht überladen
}

autopointer_02.jpg


Die Memberfunktion release()

release() setzt den internen Pointer auf NULL, läßt aber das Objekt bestehen. Das Objekt kann dann über den Autopointer weder referenziert werden noch wird der Speicher freigegeben. Für das Aufräumen muß der Entwickler dann wieder selbst sorgen, was im nächsten Beispiel nicht mehr möglich ist.

void autopointer_release()
{
   cout << "autopointer release" << endl;
   std::auto_ptr<Test> ap(new Test(17));
   cout << ap->i << endl;  // OK
   Test *tpoi = ap.get();  // liefert die Adresse
   ap.release();
   tpoi = ap.get();
   cout << tpoi << endl;  // liefert jetzt NULL
   //cout << ap->i << endl;  // Runtimeerror
}

autopointer_02a.jpg


Die Memberfunktion reset()

reset() zerstört das Objekt (ruft den Destruktor) und setzt anschließend den internen Pointer auf NULL wenn kein neuer Zeiger übergeben wird, bzw. übernimmt den Zeiger der übergeben wird.

void autopointer_reset1()
{
   cout << "autopointer reset1" << endl;
   std::auto_ptr<Test> ap(new Test(17));
   cout << ap->i << endl;  // OK
   Test *tpoi = ap.get();  // liefert die Adresse
   ap.reset();
   tpoi = ap.get();
   cout << tpoi << endl;  // jetzt NULL
}

autopointer_02b.jpg

reset() wird ein neuer Pointer übergeben.

void autopointer_reset2()
{
   cout << "autopointer reset2" << endl;
   std::auto_ptr<Test> ap(new Test(17));
   cout << ap->i << endl;  // OK
   Test *tpoi = ap.get();  // liefert die Adresse
   ap.reset(new Test(42));
   tpoi = ap.get();
   cout << tpoi << endl;  // Adresse von new Test(42)
}

Nun wird der Destruktor zweimal gerufen.

autopointer_03.jpg


Die Operatorfunktion operator=() und "transfer of ownership"

Die ungewöhnliche Überladung des Zuweisungsoperators hat letztendlich dazu geführt, daß Autopointer ab der Version C++ 11 deprecated gemacht worden sind. Nach einer Zuweisung ruft nämlich der überladenen Operator automatisch release() für den rechtsstehenden Autopointer auf, sodaß dieser nach dem Kopiervorgang auf NULL zeigt.

void transfer_of_ownership()
{
   cout << "transfer_of_ownership\n" ;

   std::auto_ptr<Test> ap1(new Test(17));
   cout << ap1->i << endl;  // OK
   std::auto_ptr<Test> ap2 = ap1;
   Test *tpoi = ap1.get();
   cout << tpoi << endl;  // NULL
   cout << ap2->i << endl;  // OK
}

autopointer_04.jpg

Der Grund hierfür ist, daß sonst zwei Autopointer existieren würden, die auf denselben Speicherbereich zeigen und so würde der Speicher zweimal freigegeben, was nicht zulässig ist. Der große Nachteil allerdings ist, daß nach der Übergabe des Autopointers an eine (Member-)Funktion der originale Pointer nicht mehr verwendbar ist, bei einer call-by-value Übergabe ja eine Kopie angelegt wird.

Ein weiterer Nachteil ist, daß Autopointer keine Arrays unterstützen.

Aus diesem Grund gibt es ab C++ 11 neue Pointertemplates, die sog. Smartpointer: shared pointer, unique pointer und weak pointer. Diese werden in den nächsten Abschnitten vorgestellt.
















Valid XHTML 1.0 Strict top Back Next Up Home