Lebenszeit und -ort eines Objekts

Content

Objekte können global bzw. statisch, lokal auf dem Stack oder auf dem Heap leben. Ihre Lebenszeit ist entsprechend. Zusätzlich gibt es auch noch thread_local-Deklarationen, mit denen wir uns hier aber nicht näher beschäftigen.

Globale bzw. statische Variablen

Bei globalen bzw. statischen Variablen steht die Adresse beim Zusammenbau des Programms bereits fest (daher statisch im Gegensatz zu dynamisch, d.h. zur Laufzeit). Entsprechende Objekte werden vor dem Aufruf von main konstruiert und nach dem Ende von main bzw. dem Aufruf von exit abgebaut. Eine Ausnahme davon sind mit static deklarierte Objekte innerhalb einer Funktion oder Methode. Diese werden erst konstruiert, wenn die Ausführung die entsprechende Deklaration das erste Mal erreicht. Dies ist seit C++11 auch thread-safe.

Alle Deklarationen sind im folgenden Beispiel global bzw. statisch:

#include <iostream>

class X {
   private:
      const char* name;
   public:
      X(const char* name) : name(name) {
	 std::cout << "X \"" << name << "\" constructed" << std::endl;
      }
      ~X() {
	 std::cout << "X \"" << name << "\" destructed" << std::endl;
      }
};

class Y {
   static X x; // static member, defined below
};

X Y::x = "static member of Y"; // definition of Y::x

X x = "global variable";

int main() {
   std::cout << "main starts" << std::endl;
   static X x = "static variable within main";
   std::cout << "main ends" << std::endl;
}
theon$ g++ -Wall -o global global.cpp
theon$ ./global
X "static member of Y" constructed
X "global variable" constructed
main starts
X "static variable within main" constructed
main ends
X "static variable within main" destructed
X "global variable" destructed
X "static member of Y" destructed
theon$ 

Es ist hier wiederum zu beobachten, dass alle Objekte in umgekehrter Reihenfolge abgebaut werden. Das ist bei innerhalb einer Funktion oder Methode statisch deklarierten Variablen nicht trivial umzusetzen, da die Reihenfolge der Konstruktionen nicht vorhersehbar ist:

#include <iostream>

class X {
   private:
      const char* name;
   public:
      X(const char* name) : name(name) {
	 std::cout << "X \"" << name << "\" constructed" << std::endl;
      }
      ~X() {
	 std::cout << "X \"" << name << "\" destructed" << std::endl;
      }
};

void foo(int selector) {
   if (selector) {
      static X x1 = "x1 within foo";
   }
   static X x2 = "x2 within foo";
}

int main() {
   std::cout << "main starts" << std::endl;
   int selector;
   if (std::cin >> selector) {
      foo(selector); foo(1);
   }
   std::cout << "main ends" << std::endl;
}
theon$ g++ -Wall -o static-can-be-dynamic static-can-be-dynamic.cpp
theon$ echo 0 | ./static-can-be-dynamic
main starts
X "x2 within foo" constructed
X "x1 within foo" constructed
main ends
X "x1 within foo" destructed
X "x2 within foo" destructed
theon$ echo 1 | ./static-can-be-dynamic
main starts
X "x1 within foo" constructed
X "x2 within foo" constructed
main ends
X "x2 within foo" destructed
X "x1 within foo" destructed
theon$ 

Lokale Variablen

Wenn die Programmausführung eine lokale Variablendeklaration erreicht, wird diese konstruiert. Am Ende des umgebenden Blocks (das ist das umgebende compound statement) werden die lokalen Variablen in umgekehrter Reihenfolge wieder abgebaut. Alle lokalen Variablen leben in dem entsprechenden Stack-Segment, das dem Funktionsaufruf zugeordnet ist.

#include <iostream>

class X {
   private:
      const char* name;
   public:
      X(const char* name) : name(name) {
	 std::cout << "X \"" << name << "\" constructed" << std::endl;
      }
      ~X() {
	 std::cout << "X \"" << name << "\" destructed" << std::endl;
      }
};

int main() {
   for (int i = 0; i < 2; ++i) {
      X x1 = "x1 within for loop";
      {
	 X x2 = "x1 in the inner block";
	 X x3 = "x2 in the inner block";
      }
      X x4 = "x4 at the end of the for loop";
   }
}
theon$ g++ -Wall -o local local.cpp
theon$ ./local
X "x1 within for loop" constructed
X "x1 in the inner block" constructed
X "x2 in the inner block" constructed
X "x2 in the inner block" destructed
X "x1 in the inner block" destructed
X "x4 at the end of the for loop" constructed
X "x4 at the end of the for loop" destructed
X "x1 within for loop" destructed
X "x1 within for loop" constructed
X "x1 in the inner block" constructed
X "x2 in the inner block" constructed
X "x2 in the inner block" destructed
X "x1 in the inner block" destructed
X "x4 at the end of the for loop" constructed
X "x4 at the end of the for loop" destructed
X "x1 within for loop" destructed
theon$ 

Variablen auf dem Heap

Daten können auf dem Heap mit dem new-Operator angelegt werden. Mit delete werden sie zunächst dekonstruiert und danach wird der belegte Speicher freigegeben. Bei einem Speicherleck, d.h. dem Fehlen von delete kommt es nicht zum Abbau der entsprechenden Objekte.

#include <iostream>

class X {
   private:
      const char* name;
   public:
      X(const char* name) : name(name) {
	 std::cout << "X \"" << name << "\" constructed" << std::endl;
      }
      ~X() {
	 std::cout << "X \"" << name << "\" destructed" << std::endl;
      }
};

int main() {
   X* x1p = new X{"*x1p"};
   X* x2p = new X{"*x2p"};
   delete x1p;
}
theon$ g++ -Wall -Wno-unused-variable -o heap heap.cpp
theon$ ./heap
X "*x1p" constructed
X "*x2p" constructed
X "*x1p" destructed
theon$ 

Aufgaben