Unbehandelte Ausnahme-Exception wenn man Methode verläßt. (Serialisierung, ofstream, ifstream)



  • Hallo erstmal,

    trotz meines ersten Beitrages, hoffe ich, dass mir jemand das folgende Mysterium aufklären kann 🙂

    Im Zuge des Einf. C++-Vorlesung an der Uni müssen wir ein Projekt abgeben.
    Da ich neben dem Studium recht viel Java-Code und C++ für mich eher unangenehm ist, beiße ich mir an der folgenden Exception schon seit Stunden die Zähne aus.

    Anwendungsfall:
    Wir müssen um zur Klausur zugelassen zu werden neben den Übungen ein Projekt abgeben.

    Eine klassische Studentendatenbank 😉 .

    Die Datenbank soll in einem Array eine festgelegte Anzahl an Studenten halten.

    Diese Datenbank soll in bei Bedarf in einer Datei gespeichert, und auch wieder eingelesen werden können.

    Zusätzlich sollte man wählen können, ob man beim Speichern die aktuellen Studenten "appeneded" oder das DB-File überschreibt.

    Alles schön und gut, der klassische Ansatz der hier wohl gesehen werden will ist jede Zeile ein Student, Klartext, kommasepariert.

    Da wir in der Klausur den ganzen compilierten Krempel händisch abschreiben(!!) möchten, würde ich gerne wenig Code erzeugen und dachte da natürlich an die eingebaute Serialisierungsfunktion ;-).

    Ich habe es geschafft, das ganze StudentenArray zu serialisieren und auszulesen. Das war kein Problem. Ich verwende ein zweites bool-Array um freie Plätze im Student-Array zu markieren, selbst das konnte ich hinter dem eigentlichen StudArray binär speichern und auch wieder auslesen. Alles fein.

    Doch die Anforderung lautet explizit, man soll an eine gespeicherte DB-Datei die aktuellen Studenten anhängen können.

    Also serialisierte ich seit dem jeden Studenten und schrieb diesen in die Datei. Alles gut.

    Ich lese nun die Datei solange binär ein, bis ifstream.eof() anschlägt und höre auf. Die Studenten werden erfolgreich gelesen und gespeichert. Doch wenn ich aus der LadeMethode zurück in die main springe kracht es mit folgender Meldung:

    Unbehandelte Ausnahme bei 0x1048ad54 (msvcp100d.dll) in Aufgabe2_4.exe: 0xC0000005: Zugriffsverletzung beim Schreiben an Position 0xfeeefeee.
    

    Hier der Code zum Speichern, hier läuft alles glatt:

    bool StudDatabase::save(std::string fName, bool append)
    {
    	//oeffne dateistream
    	ofstream outFile;
    	if(append)
    	{
    		outFile.open(fName,ios::binary|ios::app); 
    	}
    	else
    	{
    		outFile.open(fName,ios::binary);
    	}
    
    	if(!outFile)
    	{
    		cout<<fName<<" konnte zum Schreiben nicht geoeffnet werden."<<endl;
    		return false;
    	}
    
    	//Gehe jeden Studenten durch und schreibe seine Daten in eine Zeile der Datei
    	int i;
    	for(i=0;i<maxCap;i++)
    	{       //schaue im bool-Array nach ob der Platz belegt ist
    		if(isFreeIndex[i] == false) //Student gefunden
    		{
    			Student stud = studArray[i];
    			outFile.write((char*)&stud,sizeof(stud));
    		}
    	}
    	outFile.close();
    
    	return true;
    }
    

    Hier der Code zum Laden, es kracht IMHO explizit beim return.Die Anzahl der in die DB geladenen Datensätze wird korrekt ermittelt und kracht nicht.

    short StudDatabase::load(std::string fName)
    {
    	//Datenhalter "student"
    	Student tmp;
    
    	//Oeffne Datei
    	ifstream inFile;
    
    	inFile.open(fName,ios::binary);
    
    	if(!inFile)
    	{
    		cout<<"Datei konnte nicht geladen werden!"<<endl;
    		return 0;
    	}
    	//Setzt freiePlaetze-Array auf alles frei (zum ueberschreiben)
    	reset();
    	while(true)
    	{
    		inFile.read((char*)&tmp,sizeof(tmp));
    
    		//Pruefe ob wir erfolgreich gelesen haben
    		if(inFile.eof() == true)
    		{
    			//Nein gehe jetzt raus.
    			break;
    		}
    
    		//fuege hinzu, wenn false kein platz mehr
    		if(addToDB(tmp) == false)
    		{
    			cout<<"Datenbank enthaelt nicht genug Platz um die Datei zu laden. Restliche Datensaetze verfallen"<<endl;
    		}
    	}
    
    	inFile.close();
    
    	//zaehlt anzahl false im freeIndex-Array (quasi Anzahl belegter plaetze)
    	return count();
    	//es ist egal ob ich count() oder 2 zurück gebe. es kracht im return.
    }
    

    Nun noch die Main mit dem Aufruf der Funktionen:

    //includes
    StudDatabase db;
    
    int main(void)
    {
    
    	bool exit = false;
    
    	char t;
    
    	string tStr;
    
    	while(!exit)
    	{
    		menue();
    		cin>>t;
    
    		switch(t)
    		{
    		//..anderes zeugs
    		case'l':cout<<"Dateiname.?: ";cin>>tStr;cout<<endl;dbLoad(tStr);break; //ruft db.load(string str) auf. 
    		case's':cout<<"Dateiname.?: ";cin>>tStr;cout<<endl;dbSave(tStr,false);break; //ruft db.save(tStr,false) auf.
    		case'x':exit=true;;break;
    		default:cout<<"Eingabe nicht erkannt!"<<endl;
    		} //switch ende
    	}//while ende
    
    return 0;
    }
    

    Ich frage in der Hoffnung, dass es etwas triviales ist, was ich nicht bedacht habe. Vor Allem in der Load-Funktion.

    Notfalls schreibe ich halt tatsächlich einfach Text und parse den selber. Aber es würde mich schon interessieren wo der fehler liegt.

    Ich danke jedem schonmal wenn er bis hierhin gelesen hat 😉

    €dith:
    Hier noch der Auszug aus Student:

    class Student
    {
    private:
    	std::string vorName;
    	std::string nachName;
    	std::string mnr; //matrikelnummer
    	std::string fb; //fachbereich (evtl. mit Buchstaben)
    	std::string sg; //studiengang
    	short sem; //semester
    
    public:
    //...
    
    //Destruktor
    ~Student()
    	{
    		vorName.clear();
    		nachName.clear();
    		mnr.clear();
    		fb.clear();
    		sg.clear();
    
    		//short sem sollte automatisch gelöscht werden ?
    	}
    

    Vielen Dank und
    viele Grüße,
    clayton 🙂



  • Hallo clayton!

    Willkommen im Forum!

    Also Folgendes ist in deinem Code kritisch:

    outFile.write((char*)&stud,sizeof(stud));
    

    An dieser Stelle schreibst du alle Member des Student-Objektes in deine
    Datei. Sobald aber ein Zeiger in deinem Objekt ist, wird der Inhalt
    auf den er verweist nicht mitkopiert, sondern nur die Adresse an dem
    er sich zur Zeit befindet. Dabei reicht beispielweise schon ein std::string
    aus, der intern einen Zeiger auf den Text hat. Ich vermute mal du hast
    std::string als Member in deinem Student-Objekt.

    Wenn du die Daten aus der Datei einließt, steht einfach wieder die Adresse
    drin, auf die der Zeiger vorher verwiesen hat. Doch der ursprüngliche
    String existiert wahrscheinlich nicht mehr, so dass du auf Speicher verweist,
    der nicht zu deinem Programm gehört (-> Zugriffsverletzung).

    Als zusätzliche Anmerkung:
    Aber auch wenn er noch existieren würde, dann würde er beim Zerstören
    deines ursprünglichen Objektes und dieser eingelesenen Instanz freigegeben
    (doppelte Freigabe -> Problem)

    Warum kracht es beim Verlassen von load?
    Hier wird der Destruktor von Student aufgerufen und der wird versuchen
    deinen string(?) freizugeben, der auf den nicht mehr gültigen Speicherbereich
    verweist.

    Wie du siehst mache ich einige Vermutungen bezüglich deiner Student-Klasse.
    Ohne Kenntnis deiner Student-Klasse bleibt einem (wohl) nichts anderes übrig,
    aber es spricht (denke ich) viel dafür, dass es der Grund ist.
    Obwohl ich eigentlich ein Doppel-delete/-free als Fehlermeldung vermutet
    hätte. Vielleicht greifst du im Destruktor ja noch auf irgendwelche Daten zu.

    Hier noch einige Anmerkungen zu deinem Code:
    Kuck dir mal Referenzen und const an. Du erstellst ein paar unnötige Kopien:

    bool StudDatabase::save(const std::string& fName, bool append)
    short StudDatabase::load(const std::string& fName)
    Student& stud(studArray[i]);
    

    und der bool-Vergleich:

    if(inFile.eof())
    if(!addToDB(tmp))
    if(!isFreeIndex[i])
    

    //schaue im bool-Array nach ob der Platz belegt ist
    Hier bietet sich wahrscheinlich ein std::vector an

    btw: Serialisierst/Speicherst du wirklich den richtigen Studenten?

    Gruß,
    XSpille



  • Hallo clayton,

    die Beschreibung von deinem Projekt hört sich aber nicht unbedingt nach "binärer Serialisierung" an, sondern nach der normalen Text-Serialisierung.
    In C++ einfach durch Überladen des '<<' bzw '>>' Streaming-Operators für deine Studentenklasse.



  • XSpille schrieb:

    Hallo clayton!

    Willkommen im Forum!

    Vielen Dank schonmal für den kompetenten Empfang 🙂

    XSpille schrieb:

    Hallo clayton!

    Also Folgendes ist in deinem Code kritisch:

    outFile.write((char*)&stud,sizeof(stud));
    

    An dieser Stelle schreibst du alle Member des Student-Objektes in deine
    Datei. Sobald aber ein Zeiger in deinem Objekt ist, wird der Inhalt
    auf den er verweist nicht mitkopiert, sondern nur die Adresse an dem
    er sich zur Zeit befindet. Dabei reicht beispielweise schon ein std::string
    aus, der intern einen Zeiger auf den Text hat. Ich vermute mal du hast
    std::string als Member in deinem Student-Objekt.

    Wenn du die Daten aus der Datei einließt, steht einfach wieder die Adresse
    drin, auf die der Zeiger vorher verwiesen hat. Doch der ursprüngliche
    String existiert wahrscheinlich nicht mehr, so dass du auf Speicher verweist,
    der nicht zu deinem Programm gehört (-> Zugriffsverletzung).

    ...

    Warum kracht es beim Verlassen von load?
    Hier wird der Destruktor von Student aufgerufen und der wird versuchen
    deinen string(?) freizugeben, der auf den nicht mehr gültigen Speicherbereich
    verweist.

    Das ist es vermutlich, es kracht am Ende des Student Destruktors (ja er hat ne Menge Strings ;-))

    _String_val()
    		{	// destroy the object
    		typename _Alloc::template rebind<_Container_proxy>::other
    			_Alproxy(_Alval);
    		this->_Orphan_all(); //Hier kracht es
    		_Dest_val(_Alproxy, this->_Myproxy);
    		_Alproxy.deallocate(this->_Myproxy, 1);
    		this->_Myproxy = 0;
    		}
    

    D.h., das anscheinend erfolgreiche Laden mit korrekter Befüllung war also nur eine glückliche Fügung der Belegung des Speichers (Daten waren noch vorhanden)?

    Gibt es eine einfache Möglichkeit die Objekte samt strings zu serialisieren, ohne zusätzliche, externe Bibliotheken zu verwenden ?

    Deinen Rat die Suchparameter als const &string zu übergeben ergeben natürlich Sinn. Danke.

    Ich denke ich werde zeiteffizienthalber Th69 Ratschlag befolgen und das Schreiben und Parsen (des Textes, kein binary mehr) in den Student auslagern.

    Ich danke euch vielmals 🙂

    Viele Grüße,
    clayton



  • Noch ein Tipp, da du wenig Code schreiben möchtest:

    //Destruktor
    ~Student()
        {
            vorName.clear();
            nachName.clear();
            mnr.clear();
            fb.clear();
            sg.clear();
    
            //short sem sollte automatisch gelöscht werden ?
        }
    

    Der Destruktor ist vollkommen überflüssig, da die std::string selber den Speicher aufräumen.



  • clayton schrieb:

    Da wir in der Klausur den ganzen compilierten Krempel händisch abschreiben(!!) möchten, würde ich gerne wenig Code erzeugen und dachte da natürlich an die eingebaute Serialisierungsfunktion ;-).

    Es gibt keine eingebaute Serialisierungsfunktion.


Anmelden zum Antworten