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.
#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
-
Was gibt folgendes Programm aus? Ist die Reihenfolge der Abbauten genau umgekehrt zur Reihenfolge der Konstruktionen? Wenn nein: Woran liegt das?
#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 { public: Y(const char* name) : x("within Y"), name(name) { std::cout << "Y \"" << name << "\" constructed" << std::endl; } ~Y() { std::cout << "Y \"" << name << "\" destructed" << std::endl; } private: X x; const char* name; }; X x = "global x"; int main() { X x1 = "local x1"; Y* yp = new Y{"*yp (on heap)"}; X x2 = "local x2"; delete yp; }
-
Ergänzen Sie die folgende Vorlage mit der Klasse Chain, deren Konstruktor einen Namen und einen Zeiger auf ein nachfolgendes Mitglied erhält. Beim Abbau sollte die Klasse zuerst das nachfolgende Mitglied freigeben und dann die entsprechende Meldung ausgeben. Der Nullzeiger wird in C++ mit nullptr bezeichnet und es ist kein Problem, wenn delete mit einem Nullzeiger aufgerufen wird.
#include <iostream> class Chain { /* FIXME */ }; int main() { Chain* c1p = new Chain("chain member #1", nullptr); Chain* c2p = new Chain("chain member #2", c1p); Chain* c3p = new Chain("chain member #3", c2p); delete c3p; }
Erwartete Ausgabe:
Chain "chain member #1" constructed Chain "chain member #2" constructed Chain "chain member #3" constructed Chain "chain member #1" destructed Chain "chain member #2" destructed Chain "chain member #3" destructed
Was wäre in der Klasse Chain zu ändern, damit die Abbau-Meldungen in der umgekehrten Reihenfolge erscheinen?