Exception implementierung: Speicherzugriffsfehler



  • Hi, zurzeit versuche ich eine exception-klasse zu schreiben, die einen fehler string übergeben bekommt und diesen ausgeben kann, nebenbei sollen die implementierung-details der klasse versteckt werden. Biserher habe ich das so realisiert:

    class exception {
      public:
        exception(const std::string& error) throw();
        ~exception() throw();
        const std::string& what() const throw();
    
      private:
        class exception_impl* my;
    };
    
    class exception_impl {
      public:
        const std::string error;
    
        exception_impl(const std::string& err) :
          error(err) {
        }
        ~exception_impl() {
        }
    };
    
    exception::exception(const std::string& error) throw() :
      my(new exception_impl(error)) {
    }
    
    exception::~exception() throw() {
      delete my;
    }
    
    const std::string& exception::what() const throw() {
      return my->error;
    }
    

    Der Aufruf erfoglt dann so:

    int main(int argc, char* argv[]) {
      try {
        throw exception("exceptional");
      } catch(exception e) {
        std::cerr << e.what() << std::endl;
      }
    
      return 0;
    }
    

    Doch dabei erhalte ich dann folgende ausgabe:

    exceptional
    Speicherzugriffsfehler
    

    Die Klasse funktioniert also wie gewünscht, bis auf der Speicherzugriffsfehler. Soweit ich herausfinden konnte hängt dieser mit dem "delete my;" in zeile 27 der exception-implementation zusammen, mir is jedoch nicht bewusst wie ich dies umgehen könnte, ohne ein speicherleck zu provozieren.

    Vielen Dank für euere Hilfe!



  • Dir fehlt wohl der Kopierkonstruktor (Zuweisungsoperator wäre auch nicht schlecht).
    Wird die exception zerstört wird immer das my ebenfalls zerstört. du holst dir in dem catch nur ne Kopie, ich weiß nicht inwieweit bei dem throw schon kopiert wird. Jedenfalls sorgt der fehlende Kopierkonstruktor dafür dass nur der Pointer kopiert wird. Wird der zerstört versucht die Kopie weiterhin darauf zuzugreifen -> Segfault.



  • Du hast keinen Kopierkonstruktor und Zuweisungsoperator definiert. Das Objekt wird kopiert, und jedes Mal zerstört, wobei delete mehrfach auf den selben Speicherbereich angewendet wird.

    Verzichte auf new und benutze Wertsemantik.

    exception_impl my;
    

    Benötigt zwar die Definition von exception_impl , aber bei der Grösse ist das kein Problem. Du könntest auf diese Klasse ohnehin verzichten.



  • noch am rande:

    exception::exception(const std::string& error) throw() :
      my(new exception_impl(error)) {
    }
    

    pfui! wenn ne exception fliegen kann(und das kann sie definitiv, weil dort 2 news sind), dann schreib das throw() doch einfach nicht hin...

    bb



  • Nochmal eine Nachfrage...

    Da ich meine Copy-Constructor und den operator== immer vorbildlich implementiere ^^,
    ist mir folgendes noch nie aufgefallen.

    Ich meine in Erinnerung zu haben, dass man mich in meinen C++-Anfängen aufgeklärt
    hat, dass man (in der Regel) Objekte schmeißt, statt Zeiger darauf.
    Da war ich etwas verwundert, dass man sie als Referenz fängt, da diese nach
    meiner naiven Annahme ja auf dem Stack liegen. Jedoch hat man mir darauf gesagt,
    dass diese an einen sicheren Ort kopiert werden ohne (!!!) jedoch den Copy-
    Construktor aufzurufen.

    Jetzt habe ich gerade den Code kopiert.

    Ursprüngliche Ausgabe:

    exceptional
    test(2819) malloc: *** error for object 0x100150: double free
    *** set a breakpoint in malloc_error_break to debug
    test(2819) malloc: *** error for object 0x1001e0: double free
    *** set a breakpoint in malloc_error_break to debug
    

    Zwei Speicherzugriffsfehler, weil ja auch der interne string doppelt freigegeben wird.

    Das catch habe ich dann mit einer Referenz ersetzt:

    try {
        throw exception("exceptional");
      } catch(const exception& e) {
        std::cerr << e.what() << std::endl;
      }
    

    Ausgabe:

    exceptional
    

    Dann kriege ich keinen Speicherzugriffsfehler.
    Wenn ich jetzt eine Ausgabe im Copy-Constructor mache, dann wird sie nicht ausgegeben.

    exception::exception(const exception& e){
            std::cout << "copy" << std::endl;
    }
    
    exceptional
    

    Wenn ich jedoch den Copy-Constructor private mache, kompiliert das Programm nicht mehr.

    private:
        exception(const exception& e);
    
    test.cpp:26: error: ‘exception::exception(const exception&)’ is private
    test.cpp:44: error: within this context
    test.cpp:26: error: ‘exception::exception(const exception&)’ is private
    test.cpp:44: error: within this context
    

    Der Vollständigkeit wegen:
    Wenn ich in den ursprünglichen Code die Ausgabe im Copy-Constructor reinmache,
    erhalte ich folgendes:

    copy
    Bus error
    

    Was passiert nun beim Schmeissen einer Excpetion? Wird eine Kopie angelegt?
    Wenn ja, wie? magic oder durch Aufruf des Copy-Constructors?
    Wenn durch Aufruf des Copy-Constructors, wo ist meine Ausgabe? 🙂

    Gruß,
    CSpille

    EDIT: Vielleicht ist der Compiler noch interessant: i686-apple-darwin9-g++-4.0.1



  • Auch wenn ich noch weiß wieso, hier steht, dass ein Copy-Constructor
    nötig ist, auch wenn das Objekt nicht kopiert wird:
    http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.1

    Es scheint mir zudem so, dass ein Objekt, das geschmissen wird, intelligenterweise
    (von meinem Compiler) nicht erst auf dem Stack angelegt wird, denn
    eine Ausgabe von this im Exception-Constructor zeigt mir folgendes:

    0xbffff9a0
    0xbffff99c
    0xbffff998
    0xbffff994
    0x100280
    exceptional
    

    bei dem Code:

    try {
        exception ex1("test");
        exception ex2("test");
        exception ex3("test");
        exception ex4("test");
        throw exception("exceptional");
      } catch(const exception& e) {
        std::cerr << e.what() << std::endl;
      }
    

    und die Speicheradressen sind scheinbar völlig verschieden,
    was mich zu der Annahme führt: Keine Stack-Verwendung bei throw



  • CSpille schrieb:

    Was passiert nun beim Schmeissen einer Excpetion? Wird eine Kopie angelegt?
    Wenn ja, wie? magic oder durch Aufruf des Copy-Constructors?
    Wenn durch Aufruf des Copy-Constructors, wo ist meine Ausgabe? 🙂

    Das ist wie bei return : Normalerweise wird das Objekt kopiert, durch Optimierungen kann eine unnötige Kopie (gerade bei namenlosen temporären Objekten) aber wegfallen. Der Kopierkonstruktor muss jedoch vorhanden sein.



  • Nexus schrieb:

    CSpille schrieb:

    Was passiert nun beim Schmeissen einer Excpetion? Wird eine Kopie angelegt?
    Wenn ja, wie? magic oder durch Aufruf des Copy-Constructors?
    Wenn durch Aufruf des Copy-Constructors, wo ist meine Ausgabe? 🙂

    Das ist wie bei return : Normalerweise wird das Objekt kopiert, durch Optimierungen kann eine unnötige Kopie (gerade bei namenlosen temporären Objekten) aber wegfallen. Der Kopierkonstruktor muss jedoch vorhanden sein.

    Stimmt...
    Bei return kann die Kopie ja auch wegoptimiert werden...

    Danke Nexus!!!



  • Für mich als Anfänger sehr intressant zu lesen CSpille! Aber was sollte man jetzt nutzen?

    catch(const exception& e)
    

    oder

    catch(excpetion e)
    


  • Du solltest Referenzen fangen.

    catch (exception& e)
    catch (const exception& e)
    


  • Referenz, weil ansonsten der Kopierkonstruktor in der Funktion allenfalls selber eine Exception werfen kann und dann wird abort aufgerufen, was nicht gut ist.


Anmelden zum Antworten