Wieso gibt "das" keinen Memoryleak?
-
Interessant und verständlich erklärt, danke!
Gruß
-
Sebastian Pizer schrieb:
Dynamische Arrays werden zB schön von std::vector<> gekapselt,
...und Arrays mit fixer Länge können mit std::tr1::array abgebildet werden.
-
theliquidwave schrieb:
Ah, interessant, klingt logisch

Stimmt das mit dem Scope so, wie ich das beschrieben habe? Ist der Stack langsamer oder schneller als der Heap? Sollte man ihn benutzen oder nicht?Gruß
Ich lege alle meine Objekte auf den Stack, wenn es keinen Grund gibt, sie auf den Heap zu legen. Stackobjekte werden schneller angelegt und werden automatisch gelöscht. Dadurch brauche ich keinen Müllsammler und habe dennoch keine Memoryleaks.
Die Aussage, dass der Stack begrenzt ist, ist zwar richtig, aber in der Regel nicht wirklich relevant. Wenn ich beispielsweise einen std::vector auf den Stack lege und ihn mit ganz vielen Daten fülle, verbrauche ich dennoch nur ein paar wenige Bytes auf dem Stack. Der std::vector besteht in der Regel lediglich aus ein paar Zeigern, welche den Speicher letzten endes auf dem Heap verwalten. Das ist für den Anwender aber transparent.
Man sollte sich halt überlegen, welche Objekte man wirklich wie braucht. Beispielsweise einen Puffer kann ich so anlegen:
char buffer[8192];Dann habe ich 8k Stack verbraten. Oder aber so:
std::vector<char> buffer(8192);Dann brauche ich nur ein paar Bytes Stack. Zudem kann ich die Grösse auch dynamisch festlegen. Beispielsweise kann ich den Wert aus einer Konfigurationsdatei lesen oder ich kann den Puffer nachträglich vergrössern.
Also die Standardbibliothek bietet da schon sehr viel, um robuste Programme mit wenig Stackverbrauch zu schreiben.
-
tntnet schrieb:
Beispielsweise einen Puffer kann ich so anlegen:
char buffer[8192];Dann habe ich 8k Stack verbraten.
Vorausgesetzt der Buffer befindet sich innerhalb einer Funktion. Diese 8k werden aber sofort wieder frei, wenn die Funktion verlassen wird. Bei 1MB Stack (Windows-System) sind 8k vernachlässigbar.
tntnet schrieb:
Oder aber so:
std::vector<char> buffer(8192);Dann brauche ich nur ein paar Bytes Stack. Zudem kann ich die Grösse auch dynamisch festlegen.
Und vielfach langsamere Zugriffe als bei einem primitven Array. Aber gut, oft ist Tempo nicht das Entscheidende.
-
C++Fan 2010 schrieb:
tntnet schrieb:
Oder aber so:
std::vector<char> buffer(8192);Dann brauche ich nur ein paar Bytes Stack. Zudem kann ich die Grösse auch dynamisch festlegen.
Und vielfach langsamere Zugriffe als bei einem primitven Array. Aber gut, oft ist Tempo nicht das Entscheidende.
Das stimmt nicht. Der Standard ist so formuliert, dass ein std::vector ohne overhead implementierbar ist. Der Zugriff auf einzelne Zeichen mit dem operator[] wird in der Regel vom Compiler optimiert, so dass Zugriff auf std::vector vom Maschinencode identisch mit dem Zugriff auf ein Array ist.
Sicher ist das Anlegen des vectors langsamer, da hier ja eine Allokation stattfindet. Aber prinzipell ist es kein Unterschied, ob ich einen std::vector oder einen Zeiger mit malloc/free verwende.
Und wer es nicht glaubt kann ja so etwas machen:
std::vector<char> bufferv(8192); char* buffer = &buffer[0];Jetzt erklär mir mal, wie der buffer jetzt langsamer sein kann?
Übrigens: hier ist die Implementierung des operator[] des gcc 4.4.1
reference operator[](size_type __n) { return *(this->_M_impl._M_start + __n); }Wenn man genau hinschaut, sieht man, dass hier lediglich der Zeiger mit einem offset dereferenziert wird.
-
tntnet schrieb:
Übrigens: hier ist die Implementierung des operator[] des gcc 4.4.1
reference operator[](size_type __n) { return *(this->_M_impl._M_start + __n); }Wenn man genau hinschaut, sieht man, dass hier lediglich der Zeiger mit einem offset dereferenziert wird.
Im Regelfall sind Zugriffe auf fixe Arrays mit ein, zwei Maschinenbefehlen erledigt. Mit einem std::vector brauchst Du mindestens noch eine Indirektion (Pointerzugriff). Zudem können Allokation und Erweiterung eines vectors scheitern. Wie Du siehst hat alles seine Vor- und Nachteile.
-
C++Fan 2010 schrieb:
tntnet schrieb:
Übrigens: hier ist die Implementierung des operator[] des gcc 4.4.1
reference operator[](size_type __n) { return *(this->_M_impl._M_start + __n); }Wenn man genau hinschaut, sieht man, dass hier lediglich der Zeiger mit einem offset dereferenziert wird.
Im Regelfall sind Zugriffe auf fixe Arrays mit ein, zwei Maschinenbefehlen erledigt. Mit einem std::vector brauchst Du mindestens noch eine Indirektion (Pointerzugriff).
Wieder falsch.
Ein Zugriff auf einen std::vector kann genauso effizient sein, wie der Zugriff auf jedes andere Array, solange es keine fixe Adresse hat. Dabei stört nichtmal die unnötige Indirektion über _M_impl sonderlich.
Da nur globale/statische Variablen eine fixe Adresse haben, und man die normalerweise recht selten verwendet -> std::vector muss Zugriffe nicht bremsen.Maximal kann der Compiler sich "ein Register sparen", nämlich wenn er Stack-Arrays direkt über den Stack-Pointer/Frame-Pointer adressiert. Um ein dynamisches Array schnell zu adressieren, müsste er die Startadresse in einem Register halten, was natürlich den umgebenden Code etwas langsamer machen kann.
-
C++Fan 2010 schrieb:
tntnet schrieb:
Übrigens: hier ist die Implementierung des operator[] des gcc 4.4.1
reference operator[](size_type __n) { return *(this->_M_impl._M_start + __n); }Wenn man genau hinschaut, sieht man, dass hier lediglich der Zeiger mit einem offset dereferenziert wird.
Im Regelfall sind Zugriffe auf fixe Arrays mit ein, zwei Maschinenbefehlen erledigt. Mit einem std::vector brauchst Du mindestens noch eine Indirektion (Pointerzugriff). Zudem können Allokation und Erweiterung eines vectors scheitern. Wie Du siehst hat alles seine Vor- und Nachteile.
Also eine Allokation und Erweiterung eines fixen Arrays kann nicht scheitern, weil es einfach nicht geht. Ist das dann ein Vorteil eines fixen Arrays? Ein Smart ist sicher auch sicherer als ein Ferrari, weil ich damit nicht mit 300 Sachen gegen die Wand fahren kann.
Und ein fixes Array ist ja auch nur ein Zeiger, welcher genauso eine Indirektion erfordert. Es ist einfach egal, ob ich einen Zeiger verwende oder ob ich das in einer Klasse kapsele und dennoch den Zeiger implizit verwende. Aber so ähnlich hat das hustbaer ja auch schon gesagt.
-
Also eine Allokation und Erweiterung eines fixen Arrays kann nicht scheitern, weil es einfach nicht geht. Ist das dann ein Vorteil eines fixen Arrays?
Je nach Anwendungsfall, ja.
Ein Smart ist sicher auch sicherer als ein Ferrari, weil ich damit nicht mit 300 Sachen gegen die Wand fahren kann.
Wenn man hauptsächlich im Innenbereich von Großstädten fährt, braucht man keine 300 km/h Endgeschwindigkeit, dafür die bessere Parkplatzwahrscheinlichkeit. Wenn ich also tatsächlich kein dynamisches Array brauche, dann verzichte ich gerne auf eine zusätzliche Fehlerquelle.
-
theliquidwave schrieb:
Das ist dann wohl auch der Grund, warum der Stack innerhalb von großen Schleifen nicht so gerne benutzt wird
Vor allem bei Rekursionen mit hoher Tiefe kommt man mit dem Stack mitunter schnell in Bedrängnis...
-
Tyrdal schrieb:
Wenn ich also tatsächlich kein dynamisches Array brauche, dann verzichte ich gerne auf eine zusätzliche Fehlerquelle.
Und wenn ich eins brauche nehm ich n std::vector<T> und hab das dynamische Array mit automatischer Speicherverwaltung und ohne besagte Fehlerquelle.
-
Nein, mit besagter Fehlerquelle. Größenänderungen haben bei std::vector mit dem Heap zu schaffen und können daher schiefgehen. Ich hab nix gegen vector, aber daß Allokation auf dem Heap vorkommen kann ein Nachteil sein.
-
Tyrdal schrieb:
Nein, mit besagter Fehlerquelle. Größenänderungen haben bei std::vector mit dem Heap zu schaffen und können daher schiefgehen. Ich hab nix gegen vector, aber daß Allokation auf dem Heap vorkommen kann ein Nachteil sein.
Was passiert eher: Dass eine Heapallokation schiefgeht und eine handlebare Exception fliegt oder dass einem das Programm um die Ohren fliegt weil man ein vergleichbar großes Array auf dem Stack anlegen wollte?
-
Was ist wenn man die Wahrscheinlichkeit des Schiefgehens auf 0% beschränken möchte und der Stack berechenbar ist?
-
Tyrdal schrieb:
Wenn ich also tatsächlich kein dynamisches Array brauche, dann verzichte ich gerne auf eine zusätzliche Fehlerquelle.
Wofür es auch std::tr1::array gibt, womit man die Fehlerquellen im Umgang mit C-Arrays reduziert...
-
Tyrdal schrieb:
Was ist wenn man die Wahrscheinlichkeit des Schiefgehens auf 0% beschränken möchte und der Stack berechenbar ist?
Das ist doch der seltene Ausnahmefall. Mann kann sich immer Fälle konstruieren, wo das schlechtere plötzlich das bessere ist. Um beim Auto zu bleiben: früher gab es auch Argumente gegen den Sicherheitsgurt. Da kann man im Notfall dran hängen bleiben und dadurch nicht schnell genug aussteigen.
Wir diskutieren doch hier gerade den sparsamen Umgang mit dem Stack. Der Stack ist deutlich eingeschränkter als der Heap. Vor allem kann ich auf einen Stacküberlauf eigentlich nicht reagieren. Auf einen std::bad_alloc-Exception sehr wohl. Will ich zuverlässige Programme, sollte ich für irgendwie relevante Datenmengen den Heap bevorzugen.
Wenn ich keinen dynamischen Puffer brauche, dann nehme ich trotzdem einen std::vector und verändere die Grösse halt nicht.
-
Deswegen schrieb ich ja auch "je nach Anwendungsfall". Richtig lesen hilft.
Wenn ich keinen dynamischen Puffer brauche, dann nehme ich trotzdem einen std::vector und verändere die Grösse halt nicht.Kann man machen, finde da aber std::tr1::array passender.
-
Tyrdal schrieb:
Wenn ich keinen dynamischen Puffer brauche, dann nehme ich trotzdem einen std::vector und verändere die Grösse halt nicht.Kann man machen, finde da aber std::tr1::array passender.
Was dann auch wieder eine Entscheidungsfrage zwischen Heap und Stack ist. tr1::arrays liegen IIRC komplett auf dem Stack.
-
pumuckl schrieb:
tr1::arrays liegen IIRC komplett auf dem Stack.
Oder besser: Die Elemente sind Subobjekte von tr1::array. Natürlich kann ich auch schreiben
std::tr1::array<int,10> *p = new std::tr1::array<int,10>; delete p;
-
@pumuckl Klar das std::tr1::array macht nur ein Wrapper um nen array drum. Ich hab aber meist nur sehr kleine arrays, wenn die Größe vorher feststeht. Bei großen ist der Heap in den meisten Fällen besser. Soweit liegen unsere Meinungen nicht auseinander.
