std::string gibt Speicher nicht frei?



  • Moin!
    Ich entwickle hier derzeit auf einer Linux-Box mit g++ 3.3.5. Ich versuche mir gerade eine Art kleines Testframework für Unit-Tests zu schreiben. Natürlich will ich auch Speicherlecks ausfindig machen. Also habe ich new und delete komplett überschrieben. Dort gibt es eine Variable, die bei jedem new()-Aufruf um eins erhöht wird und bei jedem delete()-Aufruf um eins erniedrigt wird.
    Nach dem Durchlaufen aller Tests war die Differenz ungleich 0. Das machte mich stutzig und ich fing sofort an, nach Speicherlöchern zu suchen. Ich habe alles mögliche ausprobiert und konnte den Fehler mit Auskommentieren von Quellcode lokalisieren. Anscheinend gibt std::string angeforderten Speicher nicht frei. Sieht zumindest so aus. Allerdings ist das Verhalten sehr merkwürdig.

    Hier ein paar Beispiele dazu:

    void foo()
    {
       std::string str = "bla";
    } //es fehlt ein delete-Aufruf
    
    void bar()
    {
       std::string str = "bla";
       str = "jkkljjkl";
    } //es fehlt ein delete-Aufruf
    
    void foobar()
    {
       std::string s1 = "bla";
       std::string s2 = "blubb"
    } //Es fehlt auch nur genau ein delete Aufruf...
    


  • Und ganz ohne einen String anzulegen?

    Kann mir vorstellen, dass dort irgendein statischer Zeiger mit einem Stück Heap gefüllt wird, welches aber aufgrund der Tatsache dass es nur einmal jemals angefordert wird nicht abgeräumt wird. Unter Linux geht man ja generell davon aus dass zum Programmende wirklich alles dem Betriebssystem zurückgegeben wird, auch bei Speicherlecks (auch wenn das imho eine Nachlässigkeit in der Lib wäre).





  • Schon mal die Implementation von std::string angeschaut? Oder mit dem Debugger durchgesteppt?
    Und wie schauen die Signaturen deiner überladenen Operatoren aus?



  • string::clear() löscht zwar den Inhalt der Stringvariablen, verringert/löscht aber nicht den dafür verwendeten Speicher.
    Das geht z.Bsp. so

    string test("Irgend ein String");
    //Speicher freigeben
    string().swap(test);
    

    Bist du sicher, dass deine string-Implementation überhaupt new/delete verwendet?



  • Ich habe hier gerade noch ein paar sehr Merkwürdige Fehler entdeckt. Ich habe mir mal die Adressen der nicht freigegebenen Pointer ausgeben lassen und mal geschaut, wo die herkommen. Bei manchen ist ganz klar per Debugger nachvollziehbar, dass sie mittels delete wieder freigegeben werden.
    Ein Beispiel: Ich habe einen vector aus MathNode* Pointer. Also so: std::vector<MathNode*>. Irgendwann werden alle Objekte gelöscht, auf die eine Referenz im Vektor gehalten wurde:

    /*bgein() und end() gehören zu der Klasse, in der die Schleife verwendet wird und geben begin() und end() des Vectors zurück...*/
    for(std::vector<MathNode*>::iterator it = begin(); it != end(); ++it)
    		delete (*it);
    


  • Nach ein Bisschen Forschung ist mir nun aufgefallen, dass std::vector anscheinend Speicher belegt, sobald man ein Objekt mittels push_back() einfügt. Allerdings wird dieser Speicher wie auf wundersame Weise nicht freigegeben...





  • hmmm... nicht so wirklich.
    Ich habe nochmal ein Bisschen mit dem Debugger geschaut, wo genau der Speicher reserviert wird. Mehrere STL-Komponenten reservieren anscheinend Speicher im Voraus. Hier mal ein Stack-Trace bis zur Reservierung des Speichers, der nicht freigegeben wird.

    Ein vector, der beim ersten push_back() Speicher reserviert:

    13 operator new() at ../tests/NewDeleteOperator.cpp:74 0x0804e4be
    12 std::__default_alloc_template<true, 0>::_S_chunk_alloc()  0xb7f65a6b
    11 std::__default_alloc_template<true, 0>::_S_refill()  0xb7f6597d
    10 std::__default_alloc_template<true, 0>::allocate()  0xb7f65678
    9 std::__simple_alloc<MathNode*, std::__default_alloc_template<true, 0> >::allocate() at stl_alloc.h:232 0x08056b8d
    8 std::_Vector_alloc_base<MathNode*, std::allocator<MathNode*>, true>::_M_allocate() at stl_vector.h:127 0x08056b69
    7 std::vector<MathNode*, std::allocator<MathNode*> >::_M_insert_aux() at vector.tcc:236 0x080566dc
    6 std::vector<MathNode*, std::allocator<MathNode*> >::push_back() at stl_vector.h:603 0x080561c8
    

    Eine Map, die Speicher reserviert:

    14 operator new() at ../tests/NewDeleteOperator.cpp:74 0x0804e4be
    13 std::__default_alloc_template<true, 0>::_S_chunk_alloc()  0xb7f29a6b
    12 std::__default_alloc_template<true, 0>::_S_refill()  0xb7f2997d
    11 std::__default_alloc_template<true, 0>::allocate()  0xb7f29678
    10 std::__simple_alloc<std::_Rb_tree_node<std::pair<std::string const, Number*> >, std::__default_alloc_template<true, 0> >::allocate() at stl_alloc.h:232 0x080576a5
    9 std::_Rb_tree_alloc_base<std::pair<std::string const, Number*>, std::allocator<std::pair<std::string const, Number*> >, true>::_M_get_node() at stl_tree.h:564 0x08057678
    8 _Rb_tree_base() at stl_tree.h:579 0x0805764f
    7 _Rb_tree() at stl_tree.h:730 0x08057610
    6 map() at stl_map.h:144 0x080575b9
    .
    .
    .
    


  • Wie verhält sich deine Testroutine in Bezug auf statische Objekte, die mit new angelegt worden sind?
    Verwendet man z.B. Autopointer zum Löschen dieser Objekte weiss man nicht unbedingt zu welchen Zeitpunkt diese gelöscht werden.



  • Habe ich noch nicht ausprobiert, weil ich derzeit keine autopointer verwende. Was mir wirklich nützen würde wäre eine Funktion, mit der ich der STL sagen kann, dass Sie allen reservierten Speicher freigeben soll..



  • Wenn du feststellen möchtest ob alles korrekt freigegeben worden ist, kannst du dies mit oder ohne STL erst dann sagen, wenn das zu testende Programm beendet wurde.
    (Es sei denn du verzichtest gänzlich auf statische Strukturen und Bibliotheken, die solche intern nutzen.)

    Das automatische Erkennen von Speicherleaks ist nunmal nicht so ganz einfach, ansonsten würde wohl keiner die sehr günstigen *hust* Analysewerkzeuge zum Finden von Speicherleaks kaufen. 😃



  • Mathias schrieb:

    Das automatische Erkennen von Speicherleaks ist nunmal nicht so ganz einfach

    Naja, eigentlich schon. Du musst es halt blos in die Anwendung implementieren, und nicht hinterher versuchen, das irgendwie auf die Reihe zu bekommen. Ist bei mir gerade mal eine Headerdatei mit überladenen Operatoren new/delete und 'ner Trace Klasse.

    Ohne hackbert nahetreten zu wollen, ich glaube nicht, dass seine STL Implementation Speicherlecks verursacht. Obwohl ich darauf nicht wetten würde, immerhin werkelt er unter Linux. 😃 Ich glaube er hat irgendwo 'nen Denkfehler. Oder irgendeine krumme Geschichte ist bei seinen Tests mit im Spiel. Wann wird denn deine Zählvariable ausgegeben? Ein Problem bei mir war zB, dass ich meine Ergebnisse ausgegeben hatte, obwohl noch nicht alle Speicherfreigaben erfolgt waren. Grund dafür waren Objekte mit statischer Lebensdauer, deren ctor erst nach dem ctor meiner Trace Klasse aufgerufen worden. Evtl. ist es ein ähnliches Problem bei hackbert.


Anmelden zum Antworten