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 kein enum ??



  • 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 kein enum ??

    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 nach ntohs() .



  • 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 nach ntohs() .

    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_t

    und 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 speichern

    Jetzt 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


Log in to reply