2012-04-14 11 views
7

Grundfrage, aber ich erwartete diese Struktur zu belegen 13 Bytes Speicherplatz (1 für den char, 12 für die 3 unsigned Ints). Stattdessen gibt mir sizeof(ESPR_REL_HEADER) 16 Bytes.Schreiben Sie Raw-Struktur Inhalt (Bytes) in eine Datei in C. Verwirrt über tatsächliche Größe geschrieben

typedef struct { 
    unsigned char version; 
    unsigned int root_node_num; 
    unsigned int node_size; 
    unsigned int node_count; 
} ESPR_REL_HEADER; 

Was ich versuche diese Struktur mit einigen Werten zu tun ist, initialisieren und schreiben Sie die Daten, die sie (die rohe Bytes) an den Anfang einer Datei enthält, so dass, wenn ich diese Datei öffne ich später kann ich rekonstruiere diese Struktur und gewinne einige Metadaten darüber, was der Rest der Datei enthält.

ich die Struktur initialisiert und in die Datei wie folgt zu schreiben:

int esprime_write_btree_header(FILE * fp, unsigned int node_size) { 
    ESPR_REL_HEADER header = { 
    .version  = 1, 
    .root_node_num = 0, 
    .node_size  = node_size, 
    .node_count = 1 
    }; 

    return fwrite(&header, sizeof(ESPR_REL_HEADER), 1, fp); 
} 

Wo node_size derzeit 4, während ich Experiment ist.

Die Datei enthält folgende Daten, nachdem ich die Struktur, um es zu schreiben:

-bash$ hexdump test.dat 
0000000 01 bf f9 8b 00 00 00 00 04 00 00 00 01 00 00 00 
0000010 

Ich erwarte, dass es tatsächlich enthalten:

-bash$ hexdump test.dat 
0000000 01 00 00 00 00 04 00 00 00 01 00 00 00 
0000010 

Excuse die newbiness. Ich versuche zu lernen :) Wie schreibe ich effizient nur die Datenkomponenten meiner Struktur in eine Datei?

Antwort

6

Mikroprozessoren sind nicht zu holen Daten von beliebigen Adressen ausgelegt. Objekte wie 4-Byte int s sollten nur an Adressen gespeichert werden, die durch vier teilbar sind. Diese Anforderung wird alignment genannt.

C gibt dem Compiler die Freiheit, padding bytes zwischen Strukturelementen einzufügen, um sie auszurichten. Die Menge des Auffüllens ist nur eine Variable zwischen verschiedenen Plattformen, eine andere Hauptvariable ist endianness. Aus diesem Grund sollten Sie Strukturen nicht einfach auf die Festplatte "dumpen", wenn Sie das Programm auf mehreren Rechnern ausführen möchten.

Die beste Vorgehensweise besteht darin, jedes Mitglied explizit zu schreiben und htonl zu verwenden, um vor der binären Ausgabe endianess auf big-endian zu fixieren. Bei der Wiedergabe zu lesen, verwenden memcpy rohen Bytes zu bewegen, verwenden Sie nicht

char *buffer_ptr; 
... 
++ buffer_ptr; 
struct.member = * (int *) buffer_ptr; /* potential alignment error */ 

sondern tun

memcpy(buffer_ptr, (char *) & struct.member, sizeof struct.member); 
struct.member = ntohl(struct.member); /* if member is 4 bytes */ 
+0

Danke dafür. Also, kommt es im Grunde darauf an, ein Byte-Array manuell zu erstellen und dieses auf die Festplatte zu schreiben, und wenn ich es dann von der Festplatte herunter lese, die Bytes von diesem Array zurück in die Mitglieder einer neu zugewiesenen Struktur kopieren? Ich lerne gerade wirklich, aber ich möchte das so machen, dass die Datei immer das gleiche Format auf allen Maschinen hat, ja. – d11wtq

+1

@ d11wtq Ja, für die beste Portabilität sollten Sie 'memcpy' verwenden, um die Bytes vom Array zum Member zu kopieren und dann' ntohl' (oder was immer es auch ist) aufzurufen, um die Byte-Reihenfolge zu korrigieren. – Potatoswatter

+0

Ausgezeichnet, danke. Ich habe etwas zu lesen. Es ist schwer, Neuling zu sein :) – d11wtq

1

Wenn Sie Strukturen schreiben wie mit fwrite ist, erhalten Sie dann geschrieben, wie sie im Speicher sind, einschließlich der "toten Bytes" in der Struktur, die aufgrund der Padding eingefügt werden. Darüber hinaus werden Ihre Multi-Byte-Daten mit der Endiannes Ihres Systems geschrieben.

Wenn Sie das nicht möchten, schreiben Sie eine Funktion, die serialisiert die Daten aus Ihrer Struktur. Sie können nur die nicht aufgefüllten Bereiche schreiben und auch Multibyte-Daten in einer vorhersagbaren Reihenfolge schreiben (z. B. in der network byte order).

1

Die Struktur unterliegt Ausrichtungsregeln, was bedeutet, dass einige Elemente gepolstert werden.Es sieht so aus, als ob das erste unsigned char Feld auf 4 Bytes aufgefüllt wurde.

Einer der Fehler hier ist, dass die Regeln von System zu System unterschiedlich sein können, also wenn Sie die Struktur als Ganzes schreiben fwrite in einem Programm mit einem Compiler auf einer Plattform kompiliert, und dann versuchen, es mit zu lesen fread auf einem anderen, könnten Sie Müll bekommen, weil das zweite Programm annehmen wird, dass die Daten ausgerichtet sind, um seine Konzeption des Struktur-Layouts zu passen.

Im Allgemeinen haben Sie entweder:

  1. Entscheiden Sie, dass Datendateien gespeichert sind nur gültig für Ihr Programm erstellt, die bestimmte Merkmale aufweisen (je nach dem dokumentierten Verhalten des Compilers Sie verwendet) oder

  2. Schreiben Sie nicht eine ganze Struktur als eine, sondern implementieren Sie ein formelleres Datenformat, in dem jedes Element einzeln mit seiner explizit gesteuerten Größe geschrieben wird.

(Ein ähnliches Problem besteht darin, dass Byte-Reihenfolge anders sein könnte, die gleiche Wahl zu ihm in der Regel, mit der Ausnahme, dass in Option 2 Sie explizit die Byte-Reihenfolge des Datenformates angeben mögen.)

+0

Gibt es ein gutes Muster für Punkt (2)? Ich versuche, Festplatten-E/A in allem, was ich hier mache, zu minimieren (nicht vorzeitige Optimierung, aber das ist eigentlich der Punkt der Übung ... Ich untersuche Baumalgorithmen für das Speichern von Datensätzen auf der Festplatte mit geringem I/O-Overhead Nur viermal zu schreiben wäre ineffizient, also nehme ich an, dass ich die Daten in andere Daten in C kopieren soll, bevor ich sie schreibe? Wie ein Array von 'unsigned char' Typen? – d11wtq

+0

Die Schreibvorgänge werden oft gepuffert (Dies führt zu weniger tatsächlichen Aufrufen des Betriebssystems, um tatsächlich Daten zu schreiben.) Es ist also möglicherweise nicht so teuer, wie Sie denken. Sie könnten in einen größeren Puffer schreiben, der Ihrem Datenformat entspricht, und das dann in einem Stück "fschreiben" wahrscheinlich einfacher, wenn Ihre Daten eine feste Größe haben – Edmund

+0

Ja, das ist, was ich am Ende getan habe, die Bytes im Speicher in einen Puffer kopieren, als sie in einem Stück zu schreiben Danke, – d11wtq

0

wenn Sie die Daten in einem bestimmten Format schreiben möchten, verwenden Sie Array (e) von unsigned char ...

unsigned char outputdata[13]; 
outputdata[0] = 1; 
outputdata[1] = 0; 
/* ... of course, use data from struct ... */ 
outputdata[12] = 0; 
fwrite(outputdata, sizeof outputdata, 1, fp); 
1

Dies wegen etwas Speicherausrichtung genannt. Das erste Zeichen wird auf 4 Byte Speicher erweitert. In der Tat, größere Typen wie int können nur am Anfang eines Blocks von 4 Bytes "starten", so der Compiler Pads mit Bytes, um diesen Punkt zu erreichen.

Ich hatte das gleiche Problem mit dem Bitmap-Header, beginnend mit 2 char. Ich benutzte einen char bm[2] innerhalb der Struktur und fragte sich für 2 Tage, wo die # $%^die 3. und 4. Byte des Headers, wo gehen ...

Wenn Sie dies verhindern wollen Sie __attribute__((packed)) aber beware, memory alignment IS necessary to your program to run conveniently verwenden können.

1

Versuchen Sie es nicht! Die Größendiskrepanz wird durch die Auffüllung und Ausrichtung verursacht, die von Compilern/Linkern verwendet werden, um den Zugriff auf vars nach Geschwindigkeit zu optimieren. Die Füll- und Ausrichtungsregeln für Sprache und Betriebssystem. Darüber hinaus kann das Schreiben von Ints und das Lesen von ihnen auf unterschiedlicher Hardware aufgrund der Endianz problematisch sein.

Schreiben Sie Ihre Metadaten Byte für Byte in einer Struktur, die nicht missverstanden werden kann. Nullterminierte ASCII-Zeichenfolgen sind OK.

1

Ich benutze einen genialen Open-Source-Code von Troy D. Hanson namens TPL: http://tpl.sourceforge.net/. Mit TPL haben Sie keine externe Abhängigkeit. Es ist so einfach wie tpl.c und tpl.h in Ihr eigenes Programm zu integrieren und TPL API zu verwenden.

Hier ist die Anleitung: http://tpl.sourceforge.net/userguide.html

+0

Das sieht interessant aus, aber ich denke für meine besonderen Bedürfnisse wäre es übertrieben t bläst auch die Größe der Daten auf, indem sie den serialisierten Daten eigene Informationen hinzufügt. Meine Datei wird ein striktes Format haben (ein B-Baum, nach dem ursprünglichen Header), also sollte ich theoretisch in der Lage sein, einfach Daten aus der Datei zurück in den Speicher zu kopieren, genau wissend, was die Datentypen sind. – d11wtq

+0

+1, interessant, aber einschließlich der '.c' Datei ist die Definition einer externen Abhängigkeit. – Potatoswatter

+0

@Potatoswatter die Lizenz erlaubt Ihnen, das Programm neu zu verteilen, so dass Sie keine Probleme mit der internen Abhängigkeit von tpl.c und tpl.h haben, können Sie in Ihrem Programm bündeln. Es ist wahr, dass es die Größe aufgrund von Metadaten und String-Datendarstellung aufbläht, aber Portabilitätsprobleme und schnelle Bereitstellung können definitiv Probleme sein. – dAm2K