Domanda

Sto lavorando su un formato di file che deve essere scritta e letta in diversi sistemi operativi e computer. Alcuni di questi computer dovrebbe essere macchine x86, x86-64 altri. Alcuni possono esistere altri processori, ma non sono preoccupati per loro ancora .

Questo formato di file dovrebbe contenere diversi numeri che sarebbe stato letto in questo modo:

struct LongAsChars{
    char c1, c2, c3, c4;
};

long readLong(FILE* file){
    int b1 = fgetc(file);
    int b2 = fgetc(file);
    int b3 = fgetc(file);
    int b4 = fgetc(file);
    if(b1<0||b2<0||b3<0||b4<0){
        //throwError
    }

    LongAsChars lng;
    lng.c1 = (char) b1;
    lng.c2 = (char) b2;
    lng.c3 = (char) b3;
    lng.c4 = (char) b4;

    long* value = (long*) &lng;

    return *value;
}

e scritto come:

void writeLong(long x, FILE* f){
    long* xptr = &x;
    LongAsChars* lng = (LongAsChars*) xptr;
    fputc(lng->c1, f);
    fputc(lng->c2, f);
    fputc(lng->c3, f);
    fputc(lng->c4, f);
}

Anche se questo sembra funzionare sul mio computer, sono preoccupato che possa non in altri, o che il formato di file può finire per essere diversa tra computer (32 bit vs 64 bit computer, per esempio). Sto facendo qualcosa di sbagliato? Come devo realizzare il mio codice per utilizzare un numero costante di byte per numero?

Devo solo utilizzare fread (che sarebbe eventualmente fare il mio codice troppo veloce), invece?

È stato utile?

Soluzione

Utilizzare i tipi di stdint.h per essere sicuri di ottenere lo stesso numero di byte dentro e fuori.

Quindi sei solo lasciato con occupano di questioni endianness, che il codice probabilmente non veramente gestire.

serializzazione lungo con un char * alias ti lascia con diversi ordini di byte nel file scritto per piattaforme con differenti endianess.

Si dovrebbe scomporre il byte qualcosa in questo modo:

char c1 = (val >>  0) & 0xff;
char c2 = (val >>  8) & 0xff;
char c3 = (val >> 16) & 0xff;
char c4 = (val >> 24) & 0xff;

e ricomporre poi con qualcosa di simile:

val = (c4 << 24) |
      (c3 << 16) |
      (c2 <<  8) |
      (c1 <<  0);

Altri suggerimenti

Si potrebbe anche incorrere in problemi con endianness . Perché non usare qualcosa di simile a NetCDF o HDF , che si prendono cura di eventuali problemi di portabilità che possono sorgere?

Invece di utilizzare le strutture con i caratteri in loro, prendere in considerazione un approccio più matematico:

long l  = fgetc() << 24;
     l |= fgetc() << 16;
     l |= fgetc() <<  8;
     l |= fgetc() <<  0;

Questo è un po 'più diretto e chiaro su ciò che si sta cercando di realizzare. Può anche essere implementato in un ciclo per gestire grandi numeri.

Se non si desidera utilizzare long int. Che possono essere diverse dimensioni su piattaforme diverse, quindi è un non-starter per un formato indipendente dalla piattaforma. Dovete decidere quale intervallo di valori deve essere memorizzata nel file. 32 bit è probabilmente più semplice.

Lei dice che non sono preoccupati per altre piattaforme ancora . Lo prendo a significare che si desidera mantenere la possibilità di sostenerli, nel qual caso si dovrebbe definire l'ordine di byte del formato di file. x86 è little-endian, così si potrebbe pensare che è il migliore. Ma big-endian è l'ordine di interscambio "standard", se tutto è, dal momento che è utilizzato in rete.

Se si va per big-endian ( "ordine dei byte di rete"):

// can't be bothered to support really crazy platforms: it is in
// any case difficult even to exchange files with 9-bit machines,
// so we'll cross that bridge if we come to it.
assert(CHAR_BIT == 8);
assert(sizeof(uint32_t) == 4);

{
    // write value
    uint32_t value = 23;
    const uint32_t networkOrderValue = htonl(value);
    fwrite(&networkOrderValue, sizeof(uint32_t), 1, file);
}

{
    // read value
    uint32_t networkOrderValue;
    fread(&networkOrderValue, sizeof(uint32_t), 1, file);
    uint32_t value = ntohl(networkOrderValue);
}

In realtà, non è nemmeno bisogno di dichiarare due variabili, è solo un po 'di confusione per sostituire "valore" con il suo ordine di rete equivalente nella stessa variabile.

Funziona perché "l'ordine dei byte di rete" è definita come qualunque disposizione dei bit si traduce in un (big-endian) ordine intercambiabili in memoria. Non c'è bisogno di pasticciare con i sindacati perché qualsiasi oggetto memorizzato in C può essere trattata come una sequenza di char. Non c'è bisogno di special-case per endianness perché è quello che ntohl / htonl sono per.

Se questo è troppo lento, si può iniziare a pensare a diabolicamente ottimizzato byte-swapping specifico per la piattaforma, con SIMD o qualsiasi altra cosa. O usando little-endian, sul presupposto che la maggior parte delle vostre piattaforme saranno little-endian e quindi è più veloce "in media" su di essi. In questo caso avrete bisogno di scrivere o trovare "host a little-endian" e "little-endian per ospitare" le funzioni, che naturalmente su x86 semplicemente non fare nulla.

Credo l'approccio architettura più trasversale è quello di utilizzare i tipi uintXX_t, come definito stdint.h. Vedere pagina man qui. Ad esempio, un int32_t vi darà un intero a 32 bit su x86 e x86-64. Io uso questi per default ormai in tutto il mio codice e non hanno avuto problemi, in quanto sono abbastanza standard in tutti * NIX.

Supponendo sizeof(uint32_t) == 4, ci sono 4!=24 possibili ordini di byte, di cui little-endian e big-endian sono gli esempi più importanti, ma altri sono stati utilizzati come bene (ad esempio PDP-endian).

Qui sono funzioni per la lettura e la scrittura 32 bit interi senza segno da una corrente, ascoltando un ordine byte arbitrario che è specificato dal numero intero la cui rappresentazione è il 0,1,2,3 sequenza di byte: endian.h , endian.c

L'intestazione definisce questi prototipi

_Bool read_uint32(uint32_t * value, FILE * file, uint32_t order);
_Bool write_uint32(uint32_t value, FILE * file, uint32_t order);

e queste costanti

LITTLE_ENDIAN
BIG_ENDIAN
PDP_ENDIAN
HOST_ORDER
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top