double free or corruption



  • Hey,

    ich habe folgendes problem:

    Mein Programm wirft geplante errors/exceptions, die in der Main auch gecatcht werden, allerdings beendet sich das Programm dennoch mit einer umfangreichen Fehlernachricht:

    *** glibc detected *** ./GNU-amd64-Linux/Diplomacy: double free or corruption (fasttop): 0x00000000012d1c10 ***
    ======= Backtrace: =========
    /lib/libc.so.6(+0x774b6)[0x7f361bbd94b6]
    /lib/libc.so.6(cfree+0x73)[0x7f361bbdfc83]
    /usr/lib/libstdc++.so.6(_ZNSsD1Ev+0x39)[0x7f361c41b049]
    ./GNU-amd64-Linux/Diplomacy(_ZN11Basic_errorD1Ev+0x1c)[0x403db4]
    ./GNU-amd64-Linux/Diplomacy(_ZSt8_DestroyI11Basic_errorEvPT_+0x18)[0x403dce]
    ./GNU-amd64-Linux/Diplomacy(_ZNSt12_Destroy_auxILb0EE9__destroyIP11Basic_errorEEvT_S4_+0x1e)[0x403cb8]
    ./GNU-amd64-Linux/Diplomacy(_ZSt8_DestroyIP11Basic_errorEvT_S2_+0x23)[0x403b69]
    ./GNU-amd64-Linux/Diplomacy(_ZSt8_DestroyIP11Basic_errorS0_EvT_S2_RSaIT0_E+0x27)[0x403907]
    ./GNU-amd64-Linux/Diplomacy(_ZNSt6vectorI11Basic_errorSaIS0_EED1Ev+0x38)[0x4036c4]
    /lib/libc.so.6(__cxa_finalize+0xa0)[0x7f361bb9b8c0]
    /home/*/Dokumente/projekte/diplomacy/diplomacy/trunk/GNU-amd64-Linux/liblogik.so(+0x8556)[0x7f361c68c556]
    ======= Memory map: ========
    00400000-00406000 r-xp 00000000 08:06 6161214                            /home/*/Dokumente/projekte/diplomacy/diplomacy/trunk/GNU-amd64-Linux/Diplomacy
    00605000-00606000 r--p 00005000 08:06 6161214                            /home/*/Dokumente/projekte/diplomacy/diplomacy/trunk/GNU-amd64-Linux/Diplomacy
    00606000-00607000 rw-p 00006000 08:06 6161214                            /home/*/Dokumente/projekte/diplomacy/diplomacy/trunk/GNU-amd64-Linux/Diplomacy
    012cf000-012f0000 rw-p 00000000 00:00 0                                  [heap]
    7f3614000000-7f3614021000 rw-p 00000000 00:00 0 
    7f3614021000-7f3618000000 ---p 00000000 00:00 0 
    7f361bb62000-7f361bcdc000 r-xp 00000000 08:01 2358524                    /lib/libc-2.12.1.so
    7f361bcdc000-7f361bedb000 ---p 0017a000 08:01 2358524                    /lib/libc-2.12.1.so
    7f361bedb000-7f361bedf000 r--p 00179000 08:01 2358524                    /lib/libc-2.12.1.so
    7f361bedf000-7f361bee0000 rw-p 0017d000 08:01 2358524                    /lib/libc-2.12.1.so
    7f361bee0000-7f361bee5000 rw-p 00000000 00:00 0 
    7f361bee5000-7f361befa000 r-xp 00000000 08:01 2354767                    /lib/libgcc_s.so.1
    7f361befa000-7f361c0f9000 ---p 00015000 08:01 2354767                    /lib/libgcc_s.so.1
    7f361c0f9000-7f361c0fa000 r--p 00014000 08:01 2354767                    /lib/libgcc_s.so.1
    7f361c0fa000-7f361c0fb000 rw-p 00015000 08:01 2354767                    /lib/libgcc_s.so.1
    7f361c0fb000-7f361c17d000 r-xp 00000000 08:01 2358531                    /lib/libm-2.12.1.so
    7f361c17d000-7f361c37c000 ---p 00082000 08:01 2358531                    /lib/libm-2.12.1.so
    7f361c37c000-7f361c37d000 r--p 00081000 08:01 2358531                    /lib/libm-2.12.1.so
    7f361c37d000-7f361c37e000 rw-p 00082000 08:01 2358531                    /lib/libm-2.12.1.so
    7f361c37e000-7f361c466000 r-xp 00000000 08:01 3143249                    /usr/lib/libstdc++.so.6.0.14
    7f361c466000-7f361c665000 ---p 000e8000 08:01 3143249                    /usr/lib/libstdc++.so.6.0.14
    7f361c665000-7f361c66d000 r--p 000e7000 08:01 3143249                    /usr/lib/libstdc++.so.6.0.14
    7f361c66d000-7f361c66f000 rw-p 000ef000 08:01 3143249                    /usr/lib/libstdc++.so.6.0.14
    7f361c66f000-7f361c684000 rw-p 00000000 00:00 0 
    7f361c684000-7f361c696000 r-xp 00000000 08:06 6160791                    /home/*/Dokumente/projekte/diplomacy/diplomacy/trunk/GNU-amd64-Linux/liblogik.so
    7f361c696000-7f361c895000 ---p 00012000 08:06 6160791                    /home/*/Dokumente/projekte/diplomacy/diplomacy/trunk/GNU-amd64-Linux/liblogik.so
    7f361c895000-7f361c896000 r--p 00011000 08:06 6160791                    /home/*/Dokumente/projekte/diplomacy/diplomacy/trunk/GNU-amd64-Linux/liblogik.so
    7f361c896000-7f361c897000 rw-p 00012000 08:06 6160791                    /home/*/Dokumente/projekte/diplomacy/diplomacy/trunk/GNU-amd64-Linux/liblogik.so
    7f361c897000-7f361c8a3000 r-xp 00000000 08:06 6160771                    /home/*/Dokumente/projekte/diplomacy/diplomacy/trunk/GNU-amd64-Linux/libmap.so
    7f361c8a3000-7f361caa3000 ---p 0000c000 08:06 6160771                    /home/*/Dokumente/projekte/diplomacy/diplomacy/trunk/GNU-amd64-Linux/libmap.so
    7f361caa3000-7f361caa4000 r--p 0000c000 08:06 6160771                    /home/*/Dokumente/projekte/diplomacy/diplomacy/trunk/GNU-amd64-Linux/libmap.so
    7f361caa4000-7f361caa5000 rw-p 0000d000 08:06 6160771                    /home/*/Dokumente/projekte/diplomacy/diplomacy/trunk/GNU-amd64-Linux/libmap.so
    7f361caa5000-7f361cac5000 r-xp 00000000 08:01 2358532                    /lib/ld-2.12.1.so
    7f361cc97000-7f361cc9d000 rw-p 00000000 00:00 0 
    7f361ccc2000-7f361ccc5000 rw-p 00000000 00:00 0 
    7f361ccc5000-7f361ccc6000 r--p 00020000 08:01 2358532                    /lib/ld-2.12.1.so
    7f361ccc6000-7f361ccc7000 rw-p 00021000 08:01 2358532                    /lib/ld-2.12.1.so
    7f361ccc7000-7f361ccc8000 rw-p 00000000 00:00 0 
    7fffb7234000-7fffb7255000 rw-p 00000000 00:00 0                          [stack]
    7fffb726b000-7fffb726c000 r-xp 00000000 00:00 0                          [vdso]
    ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
    Abgebrochen
    

    Meine Frage ist nun, wieso? Auf die Dauer will ich nämlich diese Fehler intern behandeln und abarbeiten, und dafür denke ich muss diese Fehlernachricht verschwinden.

    danke für eure Hilfe

    Hier wird der Fehler geworfen

    if(parsing::parsing_error_flag) {                                           //wenn es fehler beim parsen gab
    
    #ifdef debug
            std::cout << "generating Unknown_orders exception" << std::endl;
    #endif
    
            throw Unknown_orders(parsing::fehler);                                  //verfasse eine Unknown_order excpetion
        }
    

    Hier gefangen

    catch(Unknown_orders& e) {
            for(int i = 0; i != e.size();++i)
                std::cout << e[i].get_fehlernachricht() << std::endl;
        }
    

    Dies ist der QC

    class Unknown_orders : std::exception {
    
        const std::vector<Basic_error> fehler;
    
    public:
        /** \brief Standartkonstruktor
         * 
         * @param die Fehler die beim Parsen aufgetreten sind
         */
        Unknown_orders(std::vector<Basic_error>);
    
        virtual const char* what() const throw();
    
        const std::vector<Basic_error>& get_fehler() const;
    
        virtual ~Unknown_orders() throw();
    
        const Basic_error& operator[](int const& index);
    
        const int size();  
    };
    

  • Mod

    Der Fehler scheint auf den ersten Blick nicht in dem gezeigten Code zu liegen. Der Fehler ist ein typisches Symptom für (in C++) unsaubere Programmierstrukturen. Benutzt du new/delete in deinem Programm? Oder gar malloc/free? Dies sind typische Ansatzpunkte für solche Fehler. Eventuell auch falsch benutzte rohe Arrays, oder Arrays (oder andere Container) bei denen du über die Grenzen weggeschrieben hast (vielleicht uninitialiserte Indexvariablen oder off-by-one-Fehler).
    Gute Strategien zur Vermeidung solcher Probleme sind das RAII-Idiom und die Kapselung von Arrays in Containerklassen (im Prinzip auch eine Form von RAII, aber so wichtig, dass ich es noch einmal extra nenne). Das Funktioniert dann auch besonders gut im Zusammenspiel mit Exceptions, weil dann garantiert alles sauber aufgeräumt wird. Wenn man das sauber durchzieht kann eigentlich nichts gravierendes mehr schiefgehen, außer Logikfehlern (und auch da kann man dem Compiler beibringen, viele abzufangen).

    Zur Fehlersuche in deinem Programm würde ich vorschlagen, dass du uns eventuelle new/delete-Stellen mal zeigst und/oder mal mit einem Speicherdebugger wie valgrind (mit allen Tests!) durch das Programm gehst um Fehler bei Arraygrenzen und uninitialisierten Indizes zu finden. Ein Linken gegen eine Debuglaufzeitbibliothek schadet sicher auch nicht, da findest du auch Fehler bei Fehlbenutzung der Standardbibliothek.



  • Der Backtrace sagt, dass der Fehler in Basic_error::~Basic_error() auftritt (Tip: Mit c++filt kann man die Symbolnamen entziffern). Wahrscheinlich löschst du da etwas doppelt, aber es kann natürlich auch sein, dass du dir irgendwo anders den Heap zerschossen hast.

    Was genau da passiert, kann ich natürlich nicht sagen, weil ich Basic_error nicht kenne.



  • Ich benutze gar kein new/delete, zu Fehler trächtig in der Anfangsentwicklung, von daher müsste der Fehler woanders liegen.
    Was Arrays angeht, benutze ich Vectoren, also vorgefertigte Containerklassen.

    Das ist die Basic_error Klasse

    class Basic_error {
    
        int zeile;
    
        std::string fehlernachricht; 
    
    public:
        Basic_error(int,std::string);
    
        Basic_error(const Basic_error&);
    
        const int& get_zeile() const;
    
        const std::string& get_fehlernachricht() const;
    };
    

    Eine Spekulation meinerseits wäre, dass es daran liegen könnte, dass ich in meinem Programm Fehler "Zwischenspeicher" um dann (es ist ein Datei-Parser), erst am Ende des Parsingsvorgang einen Gesamtfehler zu werfen.
    Allerdings übergebe ich dabei nie Referenzen/Zeiger an den Speichervector sondern immer nur Kopien :

    catch(const Parsing_error& e) { //catche Parsingerrors
    
    #ifdef debug
                std::cout << "parsing_error occured " << e.get_fehlernachricht() << std::endl;
    #endif
    
                parsing::fehler.push_back(Parsing_error(e));                        //und sammle sie //explizit eine Kopie anfertigen und übergeben
                parsing::parsing_error_flag = true;                                 //setze die Flag auf true
            }
    

    Hier die initalisierung der Globalen Speichervariable:

    namespace parsing {
    
              std::vector<Basic_error> fehler = std::vector<Basic_error>();
    
              /*Funktionsdefinitionen*/
    }
    

    Anmerkung: Basic_error ist die "Mutterklasse" von Parsing_Error. Ich wollte durch das Vererben eine namentliche Trennung zwischen Fehlern beim Parsen und Fehlern beim Interpreten (später) schaffen, im Grunde sind beide Klassen aber Basic_errors.



  • Jud4s schrieb:

    namespace parsing {
              
              std::vector<Basic_error> fehler = std::vector<Basic_error>();
    
              /*Funktionsdefinitionen*/
    }
    

    Anmerkung: Basic_error ist die "Mutterklasse" von Parsing_Error. Ich wollte durch das Vererben eine namentliche Trennung zwischen Fehlern beim Parsen und Fehlern beim Interpreten (später) schaffen, im Grunde sind beide Klassen aber Basic_errors.

    Wie soll das überhaupt funktionieren, wenn man einen Basisklassenvektor benutzt? Oder benutzt du in wirklichkeit einen vektor aus pointern oder einen ptr_vector?



  • Also funktionieren tut es. Ich weiß nicht worauf du anspielst sonst würde ich probieren das Rätsel zu lösen.

    Als ich mit dem Debugger durch gegangen bin (gdb) ist folgendes die letzte Meldung vor dem Speicherdump:

    (gdb) next
    __libc_start_main (main=<value optimized out>, argc=<value optimized out>, ubp_av=<value optimized out>, init=<value optimized out>, fini=<value optimized out>, 
        rtld_fini=<value optimized out>, stack_end=0x7fffffffe178) at libc-start.c:258
    258  l i bc-start.c: Datei oder  Verzeichnis nicht gefunden.
        i  n  libc-start.c
    

    Ich denke es liegt daran, dass ich eine Datei öffne (via ifstream) aber nie schließe.

    Ich habe nun um den ifstream ein Objekt gebastelt:

    class File {
    
        std::ifstream stream;
    
    public:
    
        File( const char * filename, std::ios_base::openmode mode = std::ios_base::in ) : stream(filename,mode) {
    
        }
    
        std::ifstream& get() {
            return stream;
        }
    
        ~File() {
            stream.close();
            if(stream.fail()) 
                throw std::runtime_error("failbit set");
        }
    
    };
    

    Ich hatte gehofft damit dem Fehler vor zu beugen, da ja jetzt zwangsläufig beim verlassen des Gültigkeitsbereiches auch geschlossen wird.

    Allerdings schlägt nun die Exception im Destrukor an, welche auf das failbit prüft, dass beim fehlerhaften schließen gesetzt wird.


  • Mod

    Streams brauchst du nicht zu schließen, die machen das schon von alleine wenn sie den Scope verlassen. Wenn du noch andere solche "Verbesserungen" im Programm hast, kommen wir der Sache langsam auf die Spur.



  • Lass das Programm mal mit valgrind laufen und sieh nach, ob es Fehler meldet.



  • Außerdem: Aus einem Destruktor heraus eine Exception zu werfen kann recht böse ausgehen. Der Destruktor wird nämlich auch aufgerufen, wenn das Objekt bei der Behandlung einer anderen Exception aufgerufen wird - und wenn du dann eine zweite Exception hinterherwirfst, weiß das Programm nicht mehr, was es machen soll.



  • Hab ich jetzt einen Stahlbetonträger vor dem Kopf oder kann man bei einem std::vector<T> doch nur T speichern, und keine abgeleiteten Klassen? In Java wär das ja was ganz anderes. Oder Mit einem std::vector<T*>.



  • Java ist kein C++. Nimm einen vector<T*>.



  • Cachus schrieb:

    Hab ich jetzt einen Stahlbetonträger vor dem Kopf oder kann man bei einem std::vector<T> doch nur T speichern, und keine abgeleiteten Klassen?

    Das hast du richtig erkannt - ein vector<T> speichert T-Objekte. Und wenn du versuchst, etwas anderes reinzupacken, greift das Slicing (d.h. das übergebene Objekt wird in ein blankes T zurechtgestutzt). Für polymorphe Container (die auch abgeleitete Klassen verarbeiten können) benötigst du Zeiger (entweder nackt wie in vector<T*> oder versteckt in einem boost::ptr_vector<T>).



  • Cachus schrieb:

    Hab ich jetzt einen Stahlbetonträger vor dem Kopf oder kann man bei einem std::vector<T> doch nur T speichern, und keine abgeleiteten Klassen? In Java wär das ja was ganz anderes. Oder Mit einem std::vector<T*>.

    Hast du völlig recht...
    Es wird der Copy-Konstruktor der Basisklasse aufgerufen, wodurch das Verhalten wohl anders ist als gewollt...



  • Das hast du richtig erkannt - ein vector<T> speichert T-Objekte. Und wenn du versuchst, etwas anderes reinzupacken, greift das Slicing (d.h. das übergebene Objekt wird in ein blankes T zurechtgestutzt). Für polymorphe Container (die auch abgeleitete Klassen verarbeiten können) benötigst du Zeiger (entweder nackt wie in vector<T*> oder versteckt in einem boost::ptr_vector<T>).

    Wie vermeide ich bei einer solchen Lösung Speicherlecks, weil da muss ich ja zwangsläufig die Fehler auf dem Heap initialisieren.

    Hmm, daran mag es liegen. Was meine "Verbesserung" angeht, das sollte eigentlich nur eine Schnelllösung darstellen, was anderes von dieser Art habe ich nicht.



  • Indem du boost::ptr_vector verwendest, oder einen vector<unique_ptr> oder einen vector<shared_ptr>



  • Jud4s schrieb:

    Das hast du richtig erkannt - ein vector<T> speichert T-Objekte. Und wenn du versuchst, etwas anderes reinzupacken, greift das Slicing (d.h. das übergebene Objekt wird in ein blankes T zurechtgestutzt). Für polymorphe Container (die auch abgeleitete Klassen verarbeiten können) benötigst du Zeiger (entweder nackt wie in vector<T*> oder versteckt in einem boost::ptr_vector<T>).

    Wie vermeide ich bei einer solchen Lösung Speicherlecks, weil da muss ich ja zwangsläufig die Fehler auf dem Heap initialisieren.

    Der ptr_vector kümmert sich afaik darum, die Elemente per delete freizugeben, wenn er zerstört wird. Bei einem normalen vector muß sich der Besitzer darum kümmern, vor seiner Vernichtung die Elemente zu delete'n.

    (Alternativ kannst du Smart-Pointer ala boost::shared_ptr oder std::unique_ptr (C++0x) in den vector packen - die räumen auch hinter sich auf)


Anmelden zum Antworten