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 für Objekte, deren Lebenszeit mit der eines Threads verbunden sind. Damit beschäftigen wir uns aber noch nicht im Rahmen dieser Sitzung.

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. Solche Speicherlecks können mit Hilfe des Werkzeugs valgrind festgestellt werden.

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
#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 -g -o heap heap.cpp
theon$ ./heap
X "*x1p" constructed
X "*x2p" constructed
X "*x1p" destructed
theon$ valgrind ./heap
==22485== Memcheck, a memory error detector
==22485== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==22485== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==22485== Command: ./heap
==22485== 
--22485-- WARNING: Serious error when reading debug info
--22485-- When reading debug info from /lib/amd64/ld.so.1:
--22485-- Can't make sense of .data section mapping
X "*x1p" constructed
X "*x2p" constructed
X "*x1p" destructed
==22485== 
==22485== HEAP SUMMARY:
==22485==     in use at exit: 5,136 bytes in 2 blocks
==22485==   total heap usage: 4 allocs, 2 frees, 77,848 bytes allocated
==22485== 
==22485== LEAK SUMMARY:
==22485==    definitely lost: 8 bytes in 1 blocks
==22485==    indirectly lost: 0 bytes in 0 blocks
==22485==      possibly lost: 5,128 bytes in 1 blocks
==22485==    still reachable: 0 bytes in 0 blocks
==22485==         suppressed: 0 bytes in 0 blocks
==22485== Rerun with --leak-check=full to see details of leaked memory
==22485== 
==22485== For lists of detected and suppressed errors, rerun with: -s
==22485== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
theon$ valgrind --leak-check=full ./heap
==22486== Memcheck, a memory error detector
==22486== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==22486== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==22486== Command: ./heap
==22486== 
--22486-- WARNING: Serious error when reading debug info
--22486-- When reading debug info from /lib/amd64/ld.so.1:
--22486-- Can't make sense of .data section mapping
X "*x1p" constructed
X "*x2p" constructed
X "*x1p" destructed
==22486== 
==22486== HEAP SUMMARY:
==22486==     in use at exit: 5,136 bytes in 2 blocks
==22486==   total heap usage: 4 allocs, 2 frees, 77,848 bytes allocated
==22486== 
==22486== 8 bytes in 1 blocks are definitely lost in loss record 1 of 2
==22486==    at 0xFFFA04FB6: operator new(unsigned long) (vg_replace_malloc.c:460)
==22486==    by 0x400D15: main (heap.cpp:17)
==22486== 
==22486== 5,128 bytes in 1 blocks are possibly lost in loss record 2 of 2
==22486==    at 0xFFFA04E07: malloc (vg_replace_malloc.c:397)
==22486==    by 0xFFEC0D2E1: _findbuf (in /lib/amd64/libc.so.1)
==22486==    by 0xFFEC0D532: _wrtchk (in /lib/amd64/libc.so.1)
==22486==    by 0xFFEC0F470: _fwrite_unlocked (in /lib/amd64/libc.so.1)
==22486==    by 0xFFEC0F3B8: fwrite (in /lib/amd64/libc.so.1)
==22486==    by 0xFFF533203: sputn (streambuf:456)
==22486==    by 0xFFF533203: __ostream_write<char, std::char_traits<char> > (ostream_insert.h:51)
==22486==    by 0xFFF533203: std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) (ostream_insert.h:102)
==22486==    by 0xFFF533548: std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) (ostream:611)
==22486==    by 0x400E0B: X::X(char const*) (heap.cpp:8)
==22486==    by 0x400D07: main (heap.cpp:16)
==22486== 
==22486== LEAK SUMMARY:
==22486==    definitely lost: 8 bytes in 1 blocks
==22486==    indirectly lost: 0 bytes in 0 blocks
==22486==      possibly lost: 5,128 bytes in 1 blocks
==22486==    still reachable: 0 bytes in 0 blocks
==22486==         suppressed: 0 bytes in 0 blocks
==22486== 
==22486== For lists of detected and suppressed errors, rerun with: -s
==22486== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
theon$ 

Aufgaben