2012-04-14 13 views
7

Domanda di base, ma mi aspettavo che questa struttura occupasse 13 byte di spazio (1 per il char, 12 per i 3 unsigned). Invece, sizeof(ESPR_REL_HEADER) mi dà 16 byte.Scrive contenuto struct raw (byte) in un file in C. Confuso circa la dimensione effettiva scritta

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

Quello che sto cercando di fare è inizializzare questa struct con alcuni valori e scrivere i dati in esso contenuti (i byte non elaborati) per l'inizio di un file, in modo che quando apro questo file più avanti posso ricostruire questa struttura e ottenere alcuni metadati su cosa contiene il resto del file.

che sto inizializzazione del struct e la scrittura al file in questo modo:

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); 
} 

Dove node_size è attualmente 4 mentre esperimento io.

Il file contiene i seguenti dati dopo scrivo struct ad esso:

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

mi aspetto che contiene in realtà:

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

scusate il newbiness. Sto cercando di imparare :) Come faccio a scrivere in modo efficiente solo i componenti dati della mia struttura in un file?

risposta

6

microprocessori non sono progettati per recuperare i dati da indirizzi arbitrari. Oggetti come 4-byte int devono essere memorizzati solo in indirizzi divisibili per quattro. Questo requisito è chiamato alignment.

C offre al compilatore la libertà di inserire padding bytes tra i membri della struttura per allinearli. La quantità di padding è solo una variabile tra piattaforme diverse, un'altra variabile principale è endianness. Questo è il motivo per cui non dovresti semplicemente "scaricare" le strutture sul disco se vuoi che il programma funzioni su più di una macchina.

La procedura consigliata è scrivere esplicitamente ciascun membro e utilizzare htonl per correggere endianness su big-endian prima dell'output binario. Durante la lettura di nuovo, utilizzare memcpy per spostare byte grezzi, non utilizzare

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

ma invece fare

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

Grazie per quello. Quindi, in pratica, si tratta di creare manualmente un array di byte e di scriverlo su disco, quindi quando lo rileggo, copiare i byte da quell'array nei membri di una struct appena allocata? Sto solo imparando davvero, ma mi piacerebbe farlo in un modo che significhi che il file abbia sempre lo stesso formato su tutte le macchine, sì. – d11wtq

+1

@ d11wtq Sì, per la massima portabilità è necessario utilizzare 'memcpy' per copiare i byte dall'array al membro e quindi chiamare' ntohl' (o qualsiasi cosa sia appropriata) per correggere l'ordine dei byte. – Potatoswatter

+0

Eccellente, grazie. Ho qualche lettura da fare. È difficile essere novellini :) – d11wtq

1

Quando si scrive strutture come è con fwrite, si ottiene quindi scritto come sono nella memoria, compresi i "dead byte" all'interno della struttura che vengono inseriti a causa del riempimento . Inoltre, i dati multi-byte vengono scritti con gli endiannes del sistema.

Se non si desidera che ciò accada, scrivere una funzione che serializza i dati dalla struttura. È possibile scrivere solo le aree non imbottite e anche scrivere i dati multibyte in un ordine prevedibile (ad esempio nello network byte order).

1

La struttura è soggetta alle regole di allineamento, il che significa che alcuni elementi vengono riempiti.Guardandolo, sembra che il primo campo unsigned char sia stato riempito a 4 byte.

Uno dei trucchi qui è che le regole possono essere diverse da sistema a sistema, quindi se si scrive la struct nel suo complesso usando fwrite in un programma compilato con un compilatore su una piattaforma, quindi si prova a leggerlo usando fread su un altro, è possibile ottenere spazzatura perché il secondo programma assumerà che i dati siano allineati per adattarsi alla sua concezione del layout della struttura.

In generale, si deve a uno:

  1. decidere che salvati i file di dati sono validi solo per il build del programma che condividono determinate caratteristiche (a seconda del comportamento documentato del compilatore è stato utilizzato), o

  2. Non scrivere un'intera struttura come una sola, ma implementare un formato di dati più formale in cui ogni elemento è scritto singolarmente con le sue dimensioni esplicitamente controllate.

(Un problema collegato è che l'ordine di byte potrebbe essere diversa, la stessa scelta si applica in generale anche lì, solo che in opzione 2 si desidera specificare esplicitamente l'ordine dei byte del formato dei dati.)

+0

C'è un buon schema da seguire per il punto (2)? Sto cercando di ridurre al minimo l'I/O del disco in tutto ciò che faccio qui (non l'ottimizzazione prematura, ma questo è in realtà il punto dell'esercitazione ... Sto esplorando algoritmi ad albero per la memorizzazione di set di dati su disco con basso sovraccarico I/O , solo per divertimento: scrivere quattro volte sarebbe inefficiente, quindi presumo che dovrei copiare i dati in un altro dato in C prima di scriverlo? Come un array di tipi di 'char unsigned ' – d11wtq

+0

Le scritture saranno spesso memorizzate nel buffer (risultando in un numero minore di chiamate effettive al sistema operativo per scrivere effettivamente cose), quindi potrebbe non essere costoso come pensi: potresti scrivere in un buffer più grande che corrisponde al tuo formato di dati, quindi "fwrite" in un blocco. probabilmente più facile se i tuoi dati sono di dimensione fissa – Edmund

+0

Sì, è quello che ho finito alla fine, copiare i byte in memoria in un buffer, piuttosto che scriverli in un unico blocco Grazie – d11wtq

0

Se si desidera scrivere i dati in un formato specifico, usare array (s) di 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

Questo è causa di qualcosa che si chiama allineamento memoria. Il primo char viene esteso a 4 byte di memoria. In effetti, i tipi più grandi come int possono solo "avviarsi" all'inizio di un blocco di 4 byte, quindi i pad del compilatore con byte per raggiungere questo punto.

Ho avuto lo stesso problema con l'intestazione della bitmap, a partire da 2 caratteri. Ho usato un char bm[2] all'interno della struct e mi sono chiesto per 2 giorni in cui il # $%^3 ° e 4 ° byte di intestazione dove andare ...

Se si vuole evitare questo si può utilizzare __attribute__((packed)) ma beware, memory alignment IS necessary to your program to run conveniently.

1

Prova a non farlo! La discrepanza nelle dimensioni è causata dal riempimento e dall'allineamento usati dai compilatori/linker per ottimizzare gli accessi ai vars per velocità. Le regole di riempimento e allineamento con lingua e sistema operativo. Inoltre, scrivere e interpretare su hardware diversi può essere problematico a causa dell'endianness.

Scrivi i tuoi metadati byte per byte in una struttura che non può essere fraintesa. Le stringhe ASCII con terminazioni nulle sono OK.

1

Io uso un fantastico codice open source scritto da Troy D. Hanson chiamato TPL: http://tpl.sourceforge.net/. Con TPL non hai alcuna dipendenza esterna. È semplice come includere tpl.c e tpl.h nel tuo programma e utilizzare l'API TPL.

Ecco la guida: http://tpl.sourceforge.net/userguide.html

+0

Questo sembra interessante, ma penso per i miei bisogni particolari sarebbe eccessivo t gonfia anche la dimensione dei dati aggiungendo le proprie informazioni ai dati serializzati. Il mio file avrà un formato rigoroso (un albero b, dopo l'intestazione iniziale), quindi in teoria dovrei essere in grado di copiare semplicemente i dati dal file in memoria, sapendo esattamente quali sono i tipi di dati. – d11wtq

+0

+1, interessante, ma includere il file '.c' è la definizione stessa di una dipendenza esterna. – Potatoswatter

+0

@Potatoswatter la licenza consente di ridistribuire il programma, in modo da non avere problemi con la dipendenza interna di tpl.c e tpl.h, è possibile aggregare nel programma. È vero che gonfia le dimensioni a causa dei metadati e della rappresentazione dei dati delle stringhe, ma la preoccupazione della portabilità e la rapida implementazione possono essere problemi in modo definitivo. – dAm2K