5 Stimmen

Das Lesen einer Datei in eine Struktur (C++)

Ich versuche, Daten aus einer Binärdatei zu lesen und sie in eine Struktur zu speichern. Die ersten Bytes von data.bin sind:

03 56 04 FF FF FF ...

Und meine Implementierung ist:

#include 
#include 

int main()
{
    struct header {
        unsigned char type;
        unsigned short size;
    } fileHeader;

    std::ifstream file ("data.bin", std::ios::binary);
    file.read ((char*) &fileHeader, sizeof header);

    std::cout << "typ: " << (int)fileHeader.type;
    std::cout << ", größe: " << fileHeader.size << std::endl;

}

Die Ausgabe, die ich erwartet habe, ist typ: 3, größe: 1110, aber aus irgendeinem Grund ist es typ: 3, größe: 65284, sodass im Grunde das zweite Byte in der Datei übersprungen wird. Was passiert hier?

7voto

Nawaz Punkte 339767

Eigentlich ist das Verhalten implementierungsbedingt. Was tatsächlich in Ihrem Fall passiert, ist wahrscheinlich eine Polsterung von 1 Byte nach dem type-Element der Struktur, und dann folgt das zweite Element size. Ich habe dieses Argument auf der Grundlage der Ausgabe gemacht.

Hier sind Ihre Eingangsbytes:

03 56 04 FF FF FF

Das erste Byte 03 geht zum ersten Byte der Struktur, das type ist, und Sie sehen dies als Ausgabe. Dann geht das nächste Byte 56 zum zweiten Byte, das die Polsterung ist und daher ignoriert wird, dann gehen die nächsten beiden Bytes 04 FF zu den nächsten beiden Bytes der Struktur, die size sind (die eine Größe von 2 Byte haben). Auf einer Little-Endian-Maschine wird 04 FF als 0xFF04 interpretiert, was nichts anderes als 66284 ist, was Sie als Ausgabe erhalten.

Und Sie brauchen im Grunde genommen eine kompakte Struktur, um die Polsterung herauszupressen. Verwenden Sie #pragma pack. Aber eine solche Struktur wäre im Vergleich zu einer normalen Struktur langsam. Eine bessere Option ist, die Struktur manuell zu füllen, wie folgt:

char bytes[3];
std::ifstream file ("data.bin", std::ios::binary);
file.read (bytes, sizeof bytes); //die ersten 3 Bytes lesen

//dann füllen Sie den Header manuell
fileHeader.type = bytes[0];
fileHeader.size = ((unsigned short) bytes[2] << 8) | bytes[1]; 

Ein anderer Weg, die letzte Zeile zu schreiben, ist dieser:

fileHeader.size = *reinterpret_cast(bytes+1); 

Aber das ist implementierungsabhängig, da es von der Endianness der Maschine abhängt. Auf einer Little-Endian-Maschine würde es höchstwahrscheinlich funktionieren.

Ein freundlicher Ansatz wäre dieser (implementierungsabhängig):

std::ifstream file ("data.bin", std::ios::binary);
file.read (&fileHeader.type, sizeof fileHeader.type);
file.read (reinterpret_cast(&fileHeader.size), sizeof fileHeader.size);

Aber auch hier hängt die letzte Zeile von der Endianness der Maschine ab.

1voto

gbjbaanb Punkte 50303

Nun ja, es könnte Struktur-Padding sein. Um Strukturen auf modernen Architekturen schnell arbeiten zu lassen, fügen einige Compiler Padding ein, um sie auf 4- oder 8-Byte-Grenzen auszurichten.

Dies kann mit einem Pragma oder einer Compiler-Einstellung überschrieben werden. z. B. in Visual Studio ist es /Zp

Wenn dies der Fall wäre, würde man den Wert 56 im ersten Character sehen, dann die nächsten n Bytes in das Padding einlesen und dann die nächsten 2 in das Short einlesen. Wenn das 2. Byte als Padding verloren geht, werden die nächsten 2 Bytes in das Short eingelesen. Da das Short jetzt die Daten '04 FF' enthält, entspricht dies (in Little Endian) 0xff04, was 65284 ergibt.

0voto

Castilho Punkte 3127

Compiler polst Strukturen auf Bytes, die Vielfache von 2 oder 4 sind, um den Zugriff darauf in Maschinencode einfacher zu implementieren. Ich würde #pragma pack nur verwenden, wenn es wirklich notwendig wäre, und das gilt normalerweise nur, wenn Sie auf wirklich niedriger Ebene arbeiten (wie auf Firmware-Ebene). Wikipedia-Artikel dazu.

Das passiert, weil Mikroprozessoren spezifische Operationen haben, um auf Speicher an Adressen zuzugreifen, die Vielfache von vier oder zwei sind, und das macht den Quellcode einfacher zu erstellen, er verwendet Speicher effizienter und manchmal ist der Code ein wenig schneller. Es gibt Möglichkeiten, dieses Verhalten zu stoppen, natürlich wie die #pragma pack Direktive, aber sie sind kompilerabhängig. Aber die Standardwerte des Kompilers zu überschreiben ist normalerweise eine schlechte Idee, die Kompiler-Experten hatten einen sehr guten Grund, es so zu machen.

Eine bessere Lösung wäre für mich, das mit reinem C zu lösen, was sehr, sehr einfach ist, und würde einer guten Programmierpraxis folgen, nämlich: sich niemals auf das zu verlassen, was der Compiler mit Ihren Daten auf niedriger Ebene tut.

Ich weiß, dass es einfach ist, einfach #pragma pack(1) zu schreiben, und uns allen das Gefühl gibt, dass wir direkt verstehen, was im Inneren des Computers passiert, und das begeistert jeden echten Programmierer, aber die beste Lösung ist immer die, die mit der Sprache implementiert wird, die Sie verwenden. Es ist einfacher zu verstehen und daher einfacher zu warten; es ist Standardverhalten, also sollte es überall funktionieren, und in diesem speziellen Fall ist die C-Lösung wirklich einfach und geradlinig: lesen Sie Ihre Strukturattribut für Attribut, so:

void readStruct(header &h, std::ifstream file)
{
    file.read((char*) &h.type, sizeof(char));
    file.read((char *) &h.size, sizeof(short));
}

(das wird funktionieren, wenn Sie die Struktur global definieren, natürlich)

Noch besser wäre es, da Sie mit C++ arbeiten, eine Methodenfunktion zu definieren, die das Lesen für Sie durchführt, und später einfach myObject.readData(file) aufrufen. Können Sie die Schönheit und die Einfachheit erkennen?

Einfacher zu lesen, zu warten, zu kompilieren, führt zu schnellerem und optimiertem Code, es ist Standard.

Normalerweise mag ich es nicht, mit #pragma-Direktiven zu spielen, es sei denn, ich bin mir ziemlich sicher, was ich tue. Die Auswirkungen können überraschend sein.

0voto

Cyclonecode Punkte 27439

Sie könnten eine #pragma pack Compilerdirektive verwenden, um das Polsterproblem zu umgehen:

#pragma pack(push)
#pragma pack(1)
struct header {
    unsigned char type;
    unsigned short size;
} fileHeader;
#pragma pack(pop)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X