Datenverlust von int nach char* mit reinterpret_cast?
-
Okay, das selbe hatte ich mir auch gedacht. Da es mir jetzt auch gelungen ist, den char* wieder in einen Integer umzuwandeln (Ist mir vorher wohl ein dummer Fehler unterlaufen), hat sich die Frage nun von selbst beantwortet. Anlass war der, dass mir beim Speichern von Datenstrukturen hoher Anzahl in eine Datei ständig ein Byte zuviel geschrieben wird, obwohl das bei geringen Datenmengen nicht der Fall ist. Mittlerweile bin ich am Ende meines Lateins, aber das gehört dann sicherlich in einen neuen Thread.
Danke!
-
Vielleicht zeigst du mal den Code, der dir zu viele Daten geschrieben hat.
Btw, diese Art der Umwandlung ist techniasch sinnlos - wenn du eine Zahlenwert wirklich in Binärform aufbewahren willst, lass ihn in der int-Variable (mit reinterpret_cast<> änderst du den tatsächlichen Wert nicht, sondern deutest ihn nur anders); wenn du ihn in lesbarer Form haben willst, wandle ihn vernünftig um (siehe FAQ).
-
mikey schrieb:
...beim Speichern von Datenstrukturen hoher Anzahl in eine Datei ...
Um diese (und zahllose andere= Probleme zu vermeiden, rate ich immer dringendst davon ab, das "native Binärformat" zu verwenden !
Entweder macht man das Ganze "lesbar" (so, wie es die operator<<()-Funktionen üblicherweise machen) oder denkt sich ein eigenes plattformunabhängiges Binärformat aus.Denk nur daran, was mit Deinen riesigen Datenmengen passiert, wenn der nächste Compiler seine Daten anders ablegt oder andere Paddingstrategien verwendet oder ...
Dann schreibst Du Dir an einer Binärwandlung die Finger wund und brauchst 10mal soviel Zeit wie Du jetzt in ein vernünftiges Protokoll gesteckt hättest.Gruß,
Simon2.
-
mikey schrieb:
Anlass war der, dass mir beim Speichern von Datenstrukturen hoher Anzahl in eine Datei ständig ein Byte zuviel geschrieben wird, obwohl das bei geringen Datenmengen nicht der Fall ist
Hast du vergessen, beim Schreiben das Flag für Binär zu setzen, da sonst das Zeichen '\n' (der Wert 0x0A) in "\r\n" (d.h. 0x0D 0x0A) verwandelt wird ?(zumindestens unter Windows!)
-
CStoll schrieb:
Vielleicht zeigst du mal den Code, der dir zu viele Daten geschrieben hat.
An der Kompaktzusammenfassung des Codes werde ich mich heute ranmachen, da er ziemlich verstrickt ist. Ich werde ihn dann heute hier posten.
CStoll schrieb:
Btw, diese Art der Umwandlung ist techniasch sinnlos - wenn du eine Zahlenwert wirklich in Binärform aufbewahren willst, lass ihn in der int-Variable (mit reinterpret_cast<> änderst du den tatsächlichen Wert nicht, sondern deutest ihn nur anders);
In dem oben gezeigten Zusammenhang war es natürlich sinnlos. Im Projekt allerdings muss ich die Daten deshalb umwandeln, weil std::ifstream::read() mit einem char* auf den zu schreibenden Buffer zeigt.
Allerdings muss ich an dieser Stelle betonen, dass ich den ursprünglichen Fehler mit dem zuviel geschriebenem Byte gestern doch noch beseitigt habe, ich hatte einem sizeof() versehentlich die falsche Variable übergeben. Dummerweise hat es sich dann nur bei größeren Datenmengen bemerkbar gemacht.
Doch die Geschichte ist noch nicht vorbei, es existiert dann noch ein Fehler, der dem ursprünglichen Problemposting dennoch durchaus gerecht wird. Da wird auch ein Byte zuviel geschrieben, allerdings nur bei Strings mit einer Länge von exakt 10 Zeichen incl. Terminierung. Aber wiegesagt, den Code werde ich heute in verkürzter Form noch posten.
Simon schrieb:
Um diese (und zahllose andere= Probleme zu vermeiden, rate ich immer dringendst davon ab, das "native Binärformat" zu verwenden !
Naja, ich speichere Datenstrukturen mit built-in Datentypen, also PODs ab. Bisjetzt besteht natürlich noch die Gefahr, dass die Datenbank auf einer anderen Platform fehlerhaft eingelesen wird, da da die Datentypen eine andere Größe aufweisen. Da es aber vorläufig nur auf meinem System gearbeitet wird, denke ich, ist diese Problematik vorerst zu vernachlässigen.
Simon schrieb:
oder denkt sich ein eigenes plattformunabhängiges Binärformat aus.
Da habe ich bereits eine kleine Klasse implementiert, sie repräsentiert einen Integer, der sich intern auf 4 chars stützt, die somit auf jeder Platform die selbe Größe aufweisen sollten. Viel besser wären aber natürlich die Datentypen von Boost, muss ich mir mal angucken.
Th schrieb:
Hast du vergessen, beim Schreiben das Flag für Binär zu setzen, da sonst das Zeichen '\n' (der Wert 0x0A) in "\r\n" (d.h. 0x0D 0x0A) verwandelt wird ?(zumindestens unter Windows!)
Ne, habe ich nicht. Deine Theorie ist aber trotzdem interessant, da es sich bei dem zuviel geschriebenem Byte tatsächlich um 0x0D handelt.
Okay, erstmal danke soweit für die Hilfe. Später werde ich euch dann den Code posten, wer es sich antun möchte... :xmas1:
-
mikey schrieb:
CStoll schrieb:
Btw, diese Art der Umwandlung ist techniasch sinnlos - wenn du eine Zahlenwert wirklich in Binärform aufbewahren willst, lass ihn in der int-Variable (mit reinterpret_cast<> änderst du den tatsächlichen Wert nicht, sondern deutest ihn nur anders);
In dem oben gezeigten Zusammenhang war es natürlich sinnlos. Im Projekt allerdings muss ich die Daten deshalb umwandeln, weil std::ifstream::read() mit einem char* auf den zu schreibenden Buffer zeigt.
OK, dann will ich nichts gesagt haben (ich hab in deinem Beitrag wohl das & überlesen)
-
mikey schrieb:
...
Simon schrieb:
Um diese (und zahllose andere= Probleme zu vermeiden, rate ich immer dringendst davon ab, das "native Binärformat" zu verwenden !
Naja, ich speichere Datenstrukturen mit built-in Datentypen, also PODs ab. Bisjetzt besteht natürlich noch die Gefahr, dass die Datenbank auf einer anderen Platform fehlerhaft eingelesen wird, da da die Datentypen eine andere Größe aufweisen. Da es aber vorläufig nur auf meinem System gearbeitet wird, denke ich, ist diese Problematik vorerst zu vernachlässigen....
Ich weise Dich nur auf einen hungrigen Tiger mit scharfen Zähnen hinter dem nächsten Busch hin ... es ist Deine Entscheidung, ob Du ihn ignorieren oder 2 Stunden in eine andere Route investieren möchtest.
Übrigens: Dein Ausgangsproblem gehört schon zu den erstem Schrammen, die er Dir verpasst hat ! Du solltest also nicht davon ausgehen, dass ich hier reine Theoriekonstrukte aufbaue...Kleine Tipps:
a) "bis jetzt", "vorläufig", "vorerst" sind immer die Einschätzungen, die sich als erstes als falsch erweisen.
b) schon eine andere Compilereinstellung bzgl. des Alignments kann reichen - es braucht keinen Umstieg auf Quantencomputer, um hier Probleme zu erzeugen.
c) Wenn Deine Dateien wirklich sooo groß sind (dass Du an diese Form der Platzoptimierung denken musst), wird eine spätere Migration umso mühsamer.
Gruß,
Simon2.
-
Eigentlich hatte ich ja aufgrund des Umfangs garnicht eingeplant, dieses Problem hier zu schildern, aber auf CStolls Bitte mache ich es natürlich gerne.
Der folgende Code ist soweit vollständig, es wird lediglich die Implementation der Klasse DbLoader nicht gezeigt. Da es sich aber um einen Schreibfehler beim Abspeichern handelt, ist nur relevant, dass die Klasse die vorher geschriebenen Werte wieder einliest. Deshalb habe ich den Code soweit mal kommentiert, alles andere müsste sich von selbst erklären.
#include <iostream> #include "DbLoader.h" /* ***********Arbeitsablauf************* * 1. Mapnamenlänge (SegIDLength) abspeichern. (4 Bytes) * 2. Mapnamen (SegID) abspeichern (Variabel) * 3. Strukturlänge schreiben (entspricht 4 Bytes) * 4. Anschließend die Struktur speichern. (Variabel) * 5. Danach wieder von vorne */ namespace project { const char* SegID[25] = { "Segment1", "Zwei", "Drei", "Vier", "Fuenf", "Sechs", "Sieben", "Acht", "Neun", "Zehn", "Elf", "Zwoelf", "Dreizehn", "Vierzehn", "Fuenfzehn", // Sobald dieser String geschrieben wird, kommt ein Byte zuviel mit (0x0D) "Sechszehn", // Hier ebenfalls, da es sich auch um 10 Zeichen incl. '\0' handelt "Siebzehn", "Achtzehn", "Neunzehn", "zwanzig", "einundzwanzig", "zweiundzwanzig", "dreiundzwanzig", "vierundzwanzig", "fuenfundzwanzig" }; struct buff { unsigned int mapID; unsigned int monsterID; unsigned int PosX; unsigned int PosY; }; std::ofstream out ("testdatei.txt", std::ios::app, std::ios::binary); void SaveDb(const char* SegID, const buff &buffer) { // Länge des Segment-ID Strings ermitteln (SegID) std::size_t SegIDLength = std::strlen(SegID)+1; out.seekp(std::ios_base::beg); // Die ermittelte Länge abspeichern: out.write(reinterpret_cast<char*>(&SegIDLength), sizeof(std::size_t)); // Nun folgt das Schreiben des ID-Strings (SegID) out.write(SegID, SegIDLength); // Größe der übergebenen Struktur ermitteln: std::size_t BufferSize = sizeof(buffer); // Größe in Datei schreiben... out.write(reinterpret_cast<const char*>(&BufferSize), sizeof(std::size_t)); // Und jetzt die Struktur. Man könnte sie auch mithilfe einer Schleife mehrmals // pro Segment-ID schreiben, welche dann durch den weiter unten demonstrierten // Indexoperator ausgelesen werden kann: out.write(reinterpret_cast<const char*>(&buffer), BufferSize); } } int main() { // Abzuspeichernde Struktur mit Werten initialisieren... project::buff ibuff = {1,2,3,4}; // Diese Struktur wird beim Einlesen der Daten aufgefüllt. // (Erfolgt beim Anwenden des [] Operators) project::buff buuf2; // Für jede Segment-ID (25 Stück) jeweils einmal ibuff abspeichern: for(short i=0; i<25; i++) project::SaveDb(project::SegID[i], ibuff); if(project::out.is_open()) project::out.close(); std::ifstream in ("testdatei.txt", std::ios::binary); if(!in.good()) std::cout << "Datei konnte..."; // Templateklasse auf die Struktur parametrisieren. Erster Parameter // gibt den Dateistreamhandle über, der zweite legt die Struktur fest, // die beim Einlesen der Werte standardmäßig befüllt werden soll: DbLoader <project::buff> dbloader(&in, &buuf2); try { // Gewünschte Strukturen einlesen: // Die zweite Dimension gibt an, welche Struktur zur dazugehörigen ID eingelesen werden soll. // Da vorher jeweils nur eine pro ID geschrieben wurde, nehmen wir immer [1]. std::cout << dbloader["Segment1"] [1]->mapID; std::cout << dbloader["Dreizehn"] [1]->PosX; // Crash! std::cout << dbloader["Fuenfzehn"] [1]->monsterID; // Ebenfalls Fehler, da durch das einmal zuviel geschriebene Byte die // gesamte nachfolgende Datenstruktur der Datei beschädigt wird: std::cout << dbloader["vierundzwanzig"] [1]->PosY; // Würde man zu "Fuenfzehn" und "Sechszehn" noch ein Zeichen anhängen, damit // es nichtmehr 10 Zeichen sind (und somit also ein Byte zuviel geschrieben wird) // müsste die obige Einleseaktion folgende Ausgabe auf dem Bildschirm generieren: // 1234 } catch(std::ios_base::failure ex) { std::cout << "exception: " << ex.what(); std::cin.get(); } in.close(); std::cin.get(); }Im Hexeditor offenbart sich der Fehler. Überspringen wir mal die vorhergehenden SegmentIDs, und fangen wir mal ID 'Vierzehn' an:
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00000180 09 00 00 00 56 69 65 72 7A 65 ....Vierze 00000190 68 6E 00 10 00 00 00 01 00 00 00 02 00 00 00 03 hn.............. 000001A0 00 00 00 04 00 00 00 0D 0A 00 00 00 46 75 65 6E ............Fuen 000001B0 66 7A 65 68 6E fzehn usw...Dier ersten vier Bytes vor 'Vierzehn' geben die Stringlänge an. Danach folgt der String + Terminierung. Die nächsten vier Bytes geben die Länge der nachfolgenden Struktur an, also 10 00 00 00. Dann folgen die 16 Bytes der Datenstruktur. Die '1234' kann man da schön erkennen:
00000190 01 00 00 00 02 00 00 00 03 000001A0 00 00 00 04 00 00 00 ---> 0D <---Unmittelbar danach kommt auch schon das Byte zuviel: 0D. Hierbei lässt sich erkennen, dass das Byte bereits vor dem String 'Fuenfzehn' geschrieben wird. Wäre der String keine 10 Zeichen lang, würde dieses Byte auch garnicht geschrieben werden.
Falls es sich wirklich noch jemand antun möchte, gibt's hier die Projektdatei (MSVS Express 2008) zum Download.
-
mikey schrieb:
std::ofstream out ("testdatei.txt", std::ios::app, std::ios::binary);Ich hoffe mal, das zweite Komma dort ist ein Schreibfehler (wobei ich nichtmal wußte, daß der ofstream-Ctor 3 Parameter schlucken kann). Dort solltest du die Flags besser durch | trennen.
(und dann wird das Problem auch verständlich - du öffnest den Stream im Text-Modus (was aus dem ios::binary interpretiert wird, kann ich nur raten) - und dadurch wird die Längenangabe 10 (=0x0A='\n') nach den üblichen Konvertierungsregeln zu \r\n erweitert)
btw, seekp() wird ignoriert, wenn du die Datei im Append-Modus öffnest.
-
Ohje, das war's tatsächlich. Was für 'ne Lapalie...

Also auf das wäre ich denke ich mal im Leben nicht gekommen... CStoll, ich danke dir^^ Und auch den anderen und Th, der den Fehler tatsächlich richtig gedeutet hat.
-
Ja, ich habe schon 20 Jahre Programmiererfahrung -)
P.S. bei einigen STL-Implementierungen kann man als 3. Parameter die Unix-Dateiberechtigungen angeben (welche dann bei DOS/Windows-Systemen ignoriert wird), default ist dann 0666 (the number of the beast)!
-
Verwendest du zufällig einen Borland-Compiler?

Beim BCB6 mit STLport war das zumindest auch so.
-
Tja, anscheinend hat Visual Studio auch seine Finger mit im Spiel gehabt. Denn laut Standardreferenz erwartet ifstream::read lediglich zwei Parameter:
http://cplusplus.com/reference/iostream/ifstream/ifstream.html