Binary Files C vs CPP
-
ich hab vor, eine Struct in einer Datei zu speichern. Und zwar handelt es sich um eine Binaerdatei. Fuer C funkst das wunderbar. Bisher habe ich in 90% meiner Programme nur mit den C-File-Funktionen gearbeitet. Sind halt doch irgendwie
einfach in der Handhabe. Nun möcht ich die Struct auch mit den fstream-Klassen in eine Datei bringen. Allerdings erlauben die read und write-Methoden nur char's. Habe bereits mit memcpy herumexperimentiert, aber ohne Erfolg.Hat wer einen Tipp fuer mich. Da wo "MIST" steht, hängt es.
WICHTIG:
Ich möchte nicht jeden Teil der Struct extra anfassen, Konvertieren und dann beim Auslesen wieder mühevoll zurückkonvertieren. Da bleib ich lieber bei meinem ersten Beispielstruct THighscore { std::string name; unsigned int points; }; int main(int argc, char *argv[]) { THighscore *pHighscore1 = new THighscore; THighscore *pHighscore2 = new THighscore; pHighscore1->name = "Franz"; pHighscore1->points = 125; // BEGIN C FILE *pFile; // Write pFile = fopen("highscore_c.txt", "wb"); fwrite(pHighscore1, sizeof(THighscore), 1, pFile); fclose(pFile); // Read pFile = fopen("highscore_c.txt", "rb"); fread(pHighscore2, sizeof(THighscore), 1, pFile); fclose(pFile); // END C // BEGIN CPP fstream finout; // Write finout.open("highscore_cpp.txt", ios::in|ios::binary); finout.write( /* MIST */ ); finout.close(); //Read finout.open("highscore_cpp.txt", ios::out|ios::binary); finout.read( /* MIST */ ); finout.close(); // END CPP // Output cout << pHighscore2->name << endl; cout << pHighscore2->points << endl; delete pHighscore1; delete pHighscore2; return 0; }
-
BasicMan01 schrieb:
Fuer C funkst das wunderbar.
Das glaube ich dir jetzt mal so gar nicht. Denn das was du da machst funktioniert überhaupt nicht, wenn in dem struct ein std::string ist. Mach aus dem C-Teil mal zwei Programme eins zum Lesen und eins zum Schreiben und lass dich überraschen, was da für ein Mist rauskommen wird. Bei "Franz" als Name funktioniert es mit viel Glück noch, aber nimm mal einen Namen mit mehr als 16 Zeichen.
Die wirklich saubere Art und Weise die immer funktioniert (sogar plattformübergreifend, selbst wenn man Dateien aus unterschiedlichen Plattformen austauscht) ist das Konvertieren des structs, in Fachkreisen auch Serialisierung genannt. Das ist gar nicht mal so schwer, deswegen weiß ich nicht, warum du dich so dagegen sträubst.
-
OK 1:0 fuer dich. Ich hab mich eh schon gewundert, warum bei sizeof([struct]) immer 20 rauskam. Wäre ja auch zu schön.
...
Dann werd ich deinen Tipp der Serialisierung mal beherzigen und mir da was basteln. Dann lern ich auch mal wieder was.
-
Ich habe mir zwei Funktionen gebastelt, die so diversen Speicher in einen C-String kopiert und wieder zurueck. Den resultierende C-String speichere ich in eine Datei, lese ihn wieder aus und kopiere alles wieder raus in die richtigen Variablen. Mal so platt formuliert.
template<typename T> void foo(char *target, T const &s, size_t pos=0) { memcpy(target+pos, &s, sizeof(s)); } template<typename T> void bar(T &s, const char *source, size_t pos=0) { memcpy(&s, source+pos, sizeof(s)); }Was die Funktionen tun, muss ich ja net weiter erläutern.
1. Kann ich solche Geschichten spaeter fuer meine Serialisierung verwenden?2. Was mich noch interessiert: Warum funktioniert das bei einem std::string nicht, wenn ich ein Leseprogramm und ein Schreibprogramm trenne. Aber es funktioniert, wenn ich in ein und dem selben Programm die Schreib -und Leseoperationen ausfuehre. Laut meinem Verständnis sind es blos Pointer auf einen Adressbereich, die dann beim Auslesen auf was anderes zeigen. Liege ich damit richtig?
-
Die Verwendung von memcpy geht völlig am eigentlichen Problem vorbei. Du hast dir da bloß einen eigenen Ersatz für reinterpret_cast gebastelt, das hättest du auch gleich haben können. Das Problem ist folgendes:
Wie funktioniert so ein string? Nun, das ist eigentlich nicht weiter festgelegt, aber im groben machen die meisten Implementierungen das so:
class std::string { char *data; unsigned int size; // Und möglicherweise noch ein paar mehr Member }dataist ein Zeiger auf die eigentlichen Daten. Diese Daten liegen irgendwo auf dem Heap. Wenn du deinen String mit memcpy/reinterpret_cast in eine Datei schreibst, dann schreibst du in die Datei bloß die Größe und den Wert des Zeigers (das heißt die Adresse der Daten). Die eigentlichen Daten werden nicht gespeichert, die liegen ja ganz woanders, nämlich da wodatahinzeigt. Wenn du den String wieder lädst, dann hat der aus der Datei geladene String nur die Größe und eine Speicheradresse. An der Speicheradresse liegen, nachdem du das Programm neu gestartet hast, irgendwelche Sachen, aber bestimmt nicht die eigentlichen Daten. Woher sollten sie auch kommen? Und daher geht das schief.Wenn du im gleichen Programm speicherst und lädst, hast du eine leicht andere Situation. Es wird zwar wieder nur der Zeiger mit der alten Adresse wieder hergestellt, aber an dieser Adresse liegen noch die alten Daten, da sich zwischendurch nichts verändert hat. Daher funktioniert das. Aber auch nicht richtig, denn eigentlich gehört der Speicher dem aus der Datei geladenen String nicht (das heißt er hat den Speicher nicht selbst mit new/malloc reserviert), was zu allerlei Problemen führen kann.
Und std::string ist noch eine relativ einfache Datenstruktur. Eine std::list beispielsweise hat eine viel kompliziertere Zeigerstruktur. Vergiss das Vorgehen mittels memcpy lieber, das funktioniert nunmal einfach nur bei ganz ganz einfachen Datenstrukturen und selbst da ist es nicht portabel.
Hier noch ein paar Antworten auf ungestellte Fragen:
http://www.parashift.com/c++-faq-lite/serialization.html
http://www.boost.org/doc/libs/1_42_0/libs/serialization/doc/index.html
-
Danke fuer die ausfuehliche Erklaerung
... damit bestaetigt sich meine
Vermutung zwecks Pointer. Fuer die memcpy-Geschichte hatte ich auch garnicht vor,
ganze Objekte zu uebergeben. Meine entfernte Idee bestand eher darin, die Sachen in simble Datentypen umzuwandeln. Denn Serialisierung, so hab ich es verstanden, sorgt dafuer, ein komplexes Objekt mit all seinen Verknuepfungen und Referenzen in einen Datenstrom umzuwandeln, den man dann gut speichern kann.Im Moment moechte ich bewusst keine fertige Library verwenden wie boost. Ich denke, dass es spaeter Sinn macht, wenn ich wirklich massig Objekte serialisieren muss. Im Moment will ich fuer das Verstaendnis und zum Lernen eher nur was kleines uebersichtliches selber basteln. :p
Aber ich les mich erstmal in deine Links ein ... die machen nen guten Eindruck
