Wieso gibt "das" keinen Memoryleak?



  • noobLolo schrieb:

    wenn du dir nur einmal mit new speicher holst und danach dein programm beendest wird der speicher dann auch wieder frei gegeben?

    lg lolo

    Die OS merken sich welchen Speicher du dir geholt hast. Wird dein Programm beendet wird alles was noch nicht freigegeben ist freigegeben. Es ist allerdings schlechter Stil es vom OS machen zu lassen. Alles was man mit new holt sollte man mit delete freigeben und das immer dann wenn man den Speicher deffinitiv nichtmehr braucht so das er dem System wieder zur Verfügung steht.



  • theliquidwave schrieb:

    Stimmt das mit dem Scope so, wie ich das beschrieben habe?

    Nein. Variablen sind nur innerhalb ihres (und untergeordneter Scopes) gültig. Zudem wird der Speicher einer Variable auch freigegeben. Wohlbemerkt der Variable, nicht aber bei Zeigern das Objekt, auf das der Zeiger verweist (Der Zeiger selber aber schon).

    theliquidwave schrieb:

    Ist der Stack langsamer oder schneller als der Heap? Sollte man ihn benutzen oder nicht?

    Stackvariablen sind in der Regel schneller (new/delete "kostet"). Aber der "Stack" ist nicht unbegrenzt. In der Regel verwendet man unter C++ ein Kompromiss aus beiden: z.B. zur nimmt man Verwaltung eines dynamischen Arrays die Klasse std::vector, die intern den Speicher selbst verwaltet, und diese als Stackvariable (wobei der Inhalt wahrscheinlich auf dem Heap liegt).



  • theliquidwave schrieb:

    Warum erzeugt zum Beispiel folgender Code keinen Memoryleak?

    char szBla[] = "lolololololol";
    CKlasse kKlasse;
    

    Ich dachte immer, dass C++ keinen Garbage Collector hat. Demnach dürfte der Code doch nur in Scopes des gleichen Levels und höherliegenden Scopes verfügbar sein, oder?

    Ich nehme mal an, dass das in einer Funktion steht und szBla damit eine lokale Variable ist. Dann haben wir da drei Objekte, wovon zwei interessant sind (kKlasse ist irrelevant für die Frage):

    1. ein lokales char-Array namens szBla mit automatischer Speicherdauer
    2. ein String-Literal "lololo..."

    Das String-Literal existiert von Anfang bis Ende des Programms und liegt damit in einem statischen read-only Speicherbereich. Das kannst du weder erzeugen, es ist einfach da, noch zerstören.
    Bei der Initialisierung von szBla wird das Sring-Literal Zeichen für Zeichen in das Array kopiert. szBla wird aufgrund der automatischen Speicherdauer am Ende des Scopes (also wohl der Funktion) zerstört.

    Eine weitere (Designfrage) ist, wie ihr die kKlasse nennen würdet. Normalerweise würde ich ja "CKlasse *pKlasse" machen, aber ohne das * fällt auch das p weg, und ohne alles ist es auch nicht so schön.

    wie wärs mit klasse?

    Ist der Stack langsamer oder schneller als der Heap? Sollte man ihn benutzen oder nicht?

    Stack-Allokationen und -Deallokationen gehen sehr viel schneller als auf dem Heap, im Grunde können alle Variablen einer Funktion durch eine einfache Addition auf ein Register (den Stackpointer) alloziert werden, während im Heap erstmal ein Algorithmus anläuft, der freien Speicher sucht. Außerdem werden Stack-Objekte automatisch zerstört.
    Heap benutzt man, wenn man das nicht will, weil man die Speicherdauer selbst bestimmen will; oder wenn die Objekte zu groß sind. Der Stack ist manchmal arg begrenzt.



  • Ahh 🙂
    Jetzt habe ich es verstanden, merci! (das ist dann wohl der Grund warum es heißt: "was zuletzt kam wird zuerst wieder gelöscht")
    Das ist dann wohl auch der Grund, warum der Stack innerhalb von großen Schleifen nicht so gerne benutzt wird (zumindest sehe ich das hier so im SDK mit dem ich arbeite) um den Stack nicht voll zu müllen.

    Aber nach der Theorie, dass der Scope nur für den aktuellen und tiefergeordnete gültig ist, wieso klappt dann folgendes?

    CKlasse createKlasse(void)
    {
        CKlasse Klasse;
        pClassManager->append(Klasse);
    
        return Klasse;
    }
    
    CKlasse Klasse = createKlasse();
    

    Eigentlich müsste der Inhalt von Klasse nach dem Aufruf von createKlasse ungültig sein?!

    Eine weitere Frage: Kann ich auch char-Arrays übergeben? Folgender Code klappt ja nicht:

    char[] getBla(void)
    {
        char szBla[] = "blub";
        return szBla;
    }
    

    Gruß und Danke



  • theliquidwave schrieb:

    Aber nach der Theorie, dass der Scope nur für den aktuellen und tiefergeordnete gültig ist, wieso klappt dann folgendes?

    CKlasse createKlasse(void)
    {
        CKlasse Klasse;
        pClassManager->append(Klasse);
        
        return Klasse;
    }
    
    CKlasse Klasse = createKlasse();
    

    Weil hier eine KOPIE zurückgegeben wird (Wobei der Compiler dies wegoptimieren darf).
    Nachtrag: das "(void)" als leere Parameterliste ist unter C++ eigentlich auch eher ungewöhnlich, in der Regel wird eher die Notation "()" verwendet.

    theliquidwave schrieb:

    Eine weitere Frage: Kann ich auch char-Arrays übergeben? Folgender Code klappt ja nicht:

    char[] getBla(void)
    {
        char szBla[] = "blub";
        return szBla;
    }
    

    C-Arrays sind so eine Sache (und ein C-Relikt). Unter C++ würde ich hier (je nach Anwendungsfall) entweder die Klasse std::string oder std::vector<char> verwenden.



  • theliquidwave schrieb:

    Eine weitere Frage: Kann ich auch char-Arrays übergeben? Folgender Code klappt ja nicht:

    char[] getBla(void)
    {
        char szBla[] = "blub";
        return szBla;
    }
    

    Hier würde sich ein by-reference-Parameter anbieten (wenn man schon bei C-Mitteln bleiben will). An der aufrufenden Stelle muss Speicher alloziert werden. Der Zeiger darauf wird der Funktion übergeben und diese füllt den Speicher mit "blub". Für Rückgaben muss nicht immer die "return-Rückgabe" benutzt werden.



  • Merci beaucoup an alle!

    Gruß



  • Ich denke, ein Teil des Problems ist, dass Du (theliquidwave) Arrays nicht 100%ig verstanden hast. Ein Array ist einfach nur eine Aneinanderreihung von mehreren Objekten. Das war's auch schon. Keine weitere Metainformationen (wie Länge), keine Indirektion (Ein Array ist ein Array und keine Zeiger-artige Referenz wie zB in Java).

    Zu den Typen:
    T[] --> Unvollständiger Typ, Array mit unbekannter Größe
    T[5] --> Vollständiger Typ, Array mit bekannter Größe

    Initialisierung (oder "Der Compiler zählt für uns"):

    int blah[] = {2,4,6};
    

    blah ist in Wirklichkeit vom Typ int[3]. Der Typ wird automatisch vom Compiler vervollständigt.

    char blupp[] = "hallo";
    

    blupp ist in Wirklichkeit vom Typ char[6] (inklusive Nullterminierung).

    Der Array-Typ spielt bei Funktionsparametern eine Sonderrolle:

    void foo(int xxx[]);
    

    ist dasselbe wie

    void foo(int *xxx);
    

    da der Compiler (nur bei Funktionsparametern) ein Postfix-[] auf oberster Ebene nach einem Präfix-* übersetzt. Wegen des "array to pointer decay"s, eine recht schnell greifende implizite Konvertierung von Array zum Zeiger des ersten Elements, sieht es so aus, als ob sich ein Array einer Funktion übergeben lässt. In Wirklichkeit wird nur der Zeiger auf das erste Element übertragen. Arrays lassen sich nicht so einfach Kopieren, wie "normale" Typen wie int, double, etc. Daher kannst Du ein Array auch nicht wirklich als Kopie einer Funktion übergeben oder ein Array zurückgeben (es sei denn, es wird in einer Klasse bzw Struktur verpackt).

    Als letztes will ich noch sagen, dass Deine Syntax falsch gewesen wäre. Angenommen, es gäbe Funktionen, die Arrays zurückgeben könnten, dann sähe eine Deklaration so aus

    int funktion()[5];
    

    für eine Funktion, die ein 5-elementiges int-Array zurückgibt. Natürlich muss man, wenn man die Funktion aufrufen will, wissen, die groß der Rückgabetyp ist. Daher müsste die Länge fest sein. Wie gesagt: Arrays sind Arrays und nicht etwas Zeiger-artiges, was eine Objektsammlung nur referenziert. Keine Indirektion! Wenn Du eine Funktion haben willst, die ein "Array" zurückgibt, ohne von der Signatur her zu verraten, wie groß es ist, muss das per Indirektion geschehen. ZB mit einem Vektor:

    vector<int> funktion();
    

    Arrays sind in C und C++ dermaßen "low level" (keine Indirektion, keine weiteren Metainformationen), dass sie nur in wenigen Fällen sinnvoll sind. Dynamische Arrays werden zB schön von std::vector<> gekapselt, so dass man sich um die Speicherverwaltung da weniger Sorgen machen muss.

    HTH,
    SP



  • 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.


Anmelden zum Antworten