Eleganteste Lösung für Dynamischen String
-
Ich komme von C# und versuche mein C++ zu verbessern. Zu Anfang versuche ich mich an einem NBT ("Minecraft" Tag Format) Parser .
Ich lese eine Datei im genannten NBT Format ein welche aus vielen dieser "Tags" besteht. Diese Tags liegen im Speicher hintereinander und haben unterschiedliche Größen.
Betrachtet man den Anfang eines einzelnen Tags:
#pragma pack(push, 1) struct NbtNode { TagType tag_type; //Typ des Tags (1-10) //1 Byte int16_t tag_name_length; //Länge des Tag-Namen der darauf folgt //2 byte char tag_name[tag_name_length]; //Name des Tags ... }; #pragma pack(pop)
//Damit arbeiten NbtNode* node = (NbtNode*)buffer; //buffer ist die zuvor eingelesen Datei im NBT Format node->tag_name_length; node = node->next_node; //etc
Das Problem ist natürlich, dass die Größe von tag_name erst zur Laufzeit bekannt ist (bzw. nachdem man die Datei eingelesen und interpretiert hat) und diese wichtig ist, um das struct weiterhin korrekt auf den Speicher anwenden zu können. Gibt es eine "elegante" Lösung zu diesem Problem?
struct NbtNode { TagType tag_type; int16_t tag_name_length; char tag_name[]; }
Ist zwar möglich aber die Tag-Namen sind nicht 0-terminiert.
Danke schon mal im voraus.
-
Nimm std::string.
-
Das wäre natürlich möglich wenn mein struct nicht ein bestimmtes Format/Größe einhalten müsste.
-
Muss sie nicht. Du (sie) muss nur Möglichkeiten zum de-/serialisieren haben.
-
Danke für das Stichwort.
NbtNode* node = new NbtNode(is); //is = ifstream
struct NbtNode { TagType tag_type; int16_t tag_name_length; std::string tag_name; NbtNode(std::ifstream& is) : tag_type((TagType)12), tag_name_length(0) { deserialize(is); } std::ifstream& deserialize(std::ifstream& is) { is.read((char*)&tag_type, 1); is.read((char*)&tag_name_length, 2); is.read((char*)&tag_name, tag_name_length); return is; } };
-
Wieso new? Und dein read ist falsch. Du musst erst den String auf die Größe bringen und dann in den Zeichenspeicher lesen.
-
Vivida schrieb:
tag_type((TagType)12), // ...
Schaut auch grausig aus. Ist
TagType
denn keinenum
??
-
Bin natürlich dankbar für Verbesserungsvorschläge.
Nathan schrieb:
Wieso new? Und dein read ist falsch. Du musst erst den String auf die Größe bringen und dann in den Zeichenspeicher lesen.
Wieso new? Weil unwissend und C# Gewohnheit. Hatte den String noch nicht getestet und das war natürlich kompletter Blödsinn den ich da geschrieben habe, danke.
Swordfish schrieb:
Vivida schrieb:
tag_type((TagType)12), // ...
Schaut auch grausig aus. Ist
TagType
denn keinenum
??Ist schon ein Enum aber in der Dokumentation ist kein default/empty TagType vorgesehen daher hab ich einfach eine Zahl außerhalb des Umfanges angegeben um mögliche Fehler später abzufangen. Ich stimme dir zu, dass das grausam aussieht
struct NbtNode { TagType tag_type; int16_t tag_name_length; std::string tag_name; NbtNode(std::ifstream& is) : tag_type(TagType::TAG_UNKNOWN), tag_name_length(0) { deserialize(is); } std::ifstream& deserialize(std::ifstream& is) { is.read((char*)&tag_type, 1); is.read((char*)(&tag_name_length+1), 1); //Big Endian is.read((char*)&tag_name_length, 1); tag_name.resize(tag_name_length); is.read(&tag_name[0], tag_name_length); return is; } };
-
Vivida schrieb:
is.read((char*)(&tag_name_length+1), 1); //Big Endian
Das kann nicht funktionieren. Du übergibst die Adresse von
tag_name_length
+ 2 (sizeof int16_t) und liest dann 1 Byte.
Für Big/Little-Endian swapping google nachntohs()
.
-
osdt schrieb:
Vivida schrieb:
is.read((char*)(&tag_name_length+1), 1); //Big Endian
Das kann nicht funktionieren. Du übergibst die Adresse von
tag_name_length
+ 2 (sizeof int16_t) und liest dann 1 Byte.
Für Big/Little-Endian swapping google nachntohs()
.Das wollte ich gerade hinterfrage. Hat wohl für das ein Beispiel geklappt (bei dem nächsten nicht mehr). Benutze jetzt auch ntohs, htonll etc. Ich verstehe nur noch nicht ganz warum das nicht funktioniert.
Ich dachte
&tag_name_length = 0xAdresse von int16_t
&tag_name_length + 1 = 0xAdresse von int16_t + 1 => Zweiter Byte im int16_tund ich somit aus
Original: 02 01
Gelesen: 01 02 (int16_t)
mache.
-
Das Stichwort ist Pointerarithmetik. Addierst du n zu einem Pointer erhöhst du ihn um n * sizeof(T), in diesem Fall 1 * sizeof(int16_t). Du müsstest also erst nach char* casten und dann um 1 erhöhen, denn sizeof(char) ist per Definition 1.
Der portablerere Weg ist allerdings folgender:char bytes[2]; is.read(bytes, 2); tag_name_length = (bytes[0] << 8) | bytes[1];
-
Nathan schrieb:
Der portablerere Weg ist allerdings folgender:
char bytes[2]; is.read(bytes, 2); tag_name_length = (bytes[0] << 8) | bytes[1];
Portabel in dem Sinne, dass es nur auf Little-Endian Systemen das gewünschte Ergebnis liefert. Auf Big-Endian Systemen wäre ein swap ehr kontraproduktiv.
-
osdt schrieb:
Portabel in dem Sinne, dass es nur auf Little-Endian Systemen das gewünschte Ergebnis liefert. Auf Big-Endian Systemen wäre ein swap ehr kontraproduktiv.
Nein. Unabhängig davon ob man auf einem Little oder Big-Endian System ist: Das zuerst gelesene Byte wird nach links geshiftet und bildet somit die oberen Bits, das zweite dann die unteren. Bit-Operationen sind da absolut portabel.
-
Nathan schrieb:
osdt schrieb:
Portabel in dem Sinne, dass es nur auf Little-Endian Systemen das gewünschte Ergebnis liefert. Auf Big-Endian Systemen wäre ein swap ehr kontraproduktiv.
Nein. Unabhängig davon ob man auf einem Little oder Big-Endian System ist: Das zuerst gelesene Byte wird nach links geshiftet und bildet somit die oberen Bits, das zweite dann die unteren. Bit-Operationen sind da absolut portabel.
Wow, hast Recht. Keine Ahnung weshalb ich an einen simplen Byte-Swap gedacht habe ...
-
Ich hook mal meinen eigenen Thread um keinen neuen zu eröffnen:
Ich habe die komplette "Deserialisation" implementiert und alles funktioniert fantastisch. Danke nochmal an dieser Stelle
Um von der Datei zu lesen habe ich bis jetzt immer ifstream benutzt. Die Datei muss aber vorher entpackt werden da sie komprimiert ist.
Datei lesen -> Dekomprimieren -> In einen Puffer speichernJetzt möchte ich quasi die ganze "Deserialisation" auf dem Puffer anwenden und nicht mehr auf der Datei. Online stand dass es relativ einfach sei, von ifstream auf istringstream zu wechseln. Bin mir aber nicht sicher inwiefern sich istringstream dafür eignet da die Datei ja kein Textformat ist.
Die ersten beiden bytes des Puffers sind "0A 00"
char* newbuffer = new char[newbuffersize]; ... std::istringstream iss(newbuffer);
Ich bin mir sicher dass dort bereits das Problem liegt. Jedoch möchte ich schon mal fragen ob es generell möglich ist, hier mit istringstream zu arbeiten?
iss.read(test,1)
liefert immer nur test = 0A und schreitet nie voran.
-
Wenn du direkt char* nimmst, hört der String beim ersten Nullbyte auf.
Nutz den Längenkonstruktor von std::string, bzw. direkt std::string oder std::vector<char> als Buffer.
-
std::vector<char> newbuffer(newbuffersize); inflate(origbuffer, filesize, &newbuffer[0], newbuffersize); std::istringstream iss(&newbuffer[0]);
scheint nichts verändert zu haben hmm
-
std::istringstream iss(std::string(newbuffer.begin(), newbuffer.end()));
Funktioniert dafür aber. Danke
-
BTW: Falls du die Boost Libraries zur Verfügung hast, könntest du auch Boost.Iostreams verwenden um die zlib-Dekomprimierung zu machen:
http://www.boost.org/doc/libs/1_57_0/libs/iostreams/doc/classes/zlib.html