2011-12-19 4 views
11

Sto cercando di analizzare un file bmp con fread() e quando si inizia a analizzare, inverte l'ordine dei miei byte.Perché il mio byte non funziona?

typedef struct{ 
    short magic_number; 
    int file_size; 
    short reserved_bytes[2]; 
    int data_offset; 
}BMPHeader; 
    ... 
BMPHeader header; 
    ... 

I dati esadecimali sono 42 4D 36 00 03 00 00 00 00 00 36 00 00 00; sto caricando i dati esadecimali nel struct da fread(&header,14,1,fileIn);

Il mio problema è dove il numero magico dovrebbe essere 0x424d //'BM' fread() si inverte i byte di essere 0x4d42 // 'MB'

Perché fread() fare questo e come può Lo aggiusto;

MODIFICA: Se non ero abbastanza specifico, ho bisogno di leggere l'intera porzione di dati esadecimali nella struttura non solo il numero magico. Ho scelto il numero magico solo come esempio.

+8

... il pasticcio del pane con l'ordine del morso? Hai provato a mordicchiare? – Mehrdad

+1

Non è questo 'fread' invece di' bread' per il tuo titolo? – buruzaemon

+1

scusate. Devo ancora utilizzare correttamente Auto Lions. L'ho risolto –

risposta

14

Questo non è l'errore di fread, ma della CPU, che è (apparentemente) little-endian. Cioè, la tua CPU considera il primo byte in un valore short come lo basso 8 bit, piuttosto che (come sembra che tu abbia aspettato) gli 8 bit più alti.

Ogni volta che si legge un formato di file binario, è necessario convertire in modo esplicito dall'endianness del formato del file alla endianità nativa della CPU. Lo fate con funzioni come questi:

/* CHAR_BIT == 8 assumed */ 
uint16_t le16_to_cpu(const uint8_t *buf) 
{ 
    return ((uint16_t)buf[0]) | (((uint16_t)buf[1]) << 8); 
} 
uint16_t be16_to_cpu(const uint8_t *buf) 
{ 
    return ((uint16_t)buf[1]) | (((uint16_t)buf[0]) << 8); 
} 

Voi fate il vostro fread in un uint8_t buffer di dimensione appropriata, e quindi copiare manualmente tutti i dati byte sopra al vostro BMPHeader struct, la conversione, se necessario. Che qualcosa sarebbe simile a questa:

/* note adjustments to type definition */ 
typedef struct BMPHeader 
{ 
    uint8_t magic_number[2]; 
    uint32_t file_size; 
    uint8_t reserved[4]; 
    uint32_t data_offset; 
} BMPHeader; 

/* in general this is _not_ equal to sizeof(BMPHeader) */ 
#define BMP_WIRE_HDR_LEN (2 + 4 + 4 + 4) 

/* returns 0=success, -1=error */ 
int read_bmp_header(BMPHeader *hdr, FILE *fp) 
{ 
    uint8_t buf[BMP_WIRE_HDR_LEN]; 

    if (fread(buf, 1, sizeof buf, fp) != sizeof buf) 
     return -1; 

    hdr->magic_number[0] = buf[0]; 
    hdr->magic_number[1] = buf[1]; 

    hdr->file_size = le32_to_cpu(buf+2); 

    hdr->reserved[0] = buf[6]; 
    hdr->reserved[1] = buf[7]; 
    hdr->reserved[2] = buf[8]; 
    hdr->reserved[3] = buf[9]; 

    hdr->data_offset = le32_to_cpu(buf+10); 

    return 0; 
} 

Fate non scontato che endianness della CPU è lo stesso del formato di file anche se si sa per certo che in questo momento sono la stessa cosa; tu scrivi comunque le conversioni, così che in futuro il tuo codice funzionerà senza modifiche su una CPU con l'endianità opposta.

Si può rendere la vita più facile per te, utilizzando i tipi <stdint.h> larghezza fissa, utilizzando i tipi senza segno a meno che non essere in grado di rappresentare i numeri negativi è assolutamente necessario, e non utilizzando numeri interi quando array di caratteri faranno. Ho fatto tutte queste cose nell'esempio sopra. Puoi vedere che non devi preoccuparti di convertire il numero magico di endian, perché l'unica cosa che devi fare è testare magic_number[0]=='B' && magic_number[1]=='M'.

conversione nella direzione opposta, a proposito, si presenta come segue:

void cpu_to_le16(uint8_t *buf, uint16_t val) 
{ 
    buf[0] = (val & 0x00FF); 
    buf[1] = (val & 0xFF00) >> 8; 
} 
void cpu_to_be16(uint8_t *buf, uint16_t val) 
{ 
    buf[0] = (val & 0xFF00) >> 8; 
    buf[1] = (val & 0x00FF); 
} 

Conversione di 32- quantitativi/64-bit lasciato come esercizio.

+0

Se userai 'uint32_t file_size', l'endianness è fisso su LE, quindi c'è ragione di non usare' uint16_t magic_number'. – Gabe

+0

No, perché * non si 'fread' direttamente nell'oggetto BMPHeader *. Si 'gira' in' uint8_t buf [sizeof (BMPHeader)] 'e poi si copia manualmente su ciascun campo, convertendosi quando appropriato; quindi usando una stringa di due caratteri per il numero magico si evita una conversione. Inoltre, direi che è più naturale trattare il "numero magico" come una stringa di due caratteri in ogni caso (in questo caso). – zwol

+0

@Zack come copieresti i dati in questo caso? –

2

Presumo che questo sia un problema di endian. Ad esempio, stai inserendo i byte 42 e 4D nel valore short. Ma il tuo sistema è little endian (potrei avere il nome sbagliato), che in realtà legge i byte (all'interno di un tipo di intero multi-byte) da sinistra a destra invece che da destra a sinistra.

dimostrata in questo codice:

#include <stdio.h> 

int main() 
{ 
    union { 
     short sval; 
     unsigned char bval[2]; 
    } udata; 
    udata.sval = 1; 
    printf("DEC[%5hu] HEX[%04hx] BYTES[%02hhx][%02hhx]\n" 
      , udata.sval, udata.sval, udata.bval[0], udata.bval[1]); 
    udata.sval = 0x424d; 
    printf("DEC[%5hu] HEX[%04hx] BYTES[%02hhx][%02hhx]\n" 
      , udata.sval, udata.sval, udata.bval[0], udata.bval[1]); 
    udata.sval = 0x4d42; 
    printf("DEC[%5hu] HEX[%04hx] BYTES[%02hhx][%02hhx]\n" 
      , udata.sval, udata.sval, udata.bval[0], udata.bval[1]); 
    return 0; 
} 

ha pronunciato la seguente uscita

DEC[ 1] HEX[0001] BYTES[01][00] 
DEC[16973] HEX[424d] BYTES[4d][42] 
DEC[19778] HEX[4d42] BYTES[42][4d] 

Quindi, se si vuole essere portatile è necessario rilevare l'endian-ness del vostro sistema e poi fare un byte shuffle se necessario. Ci saranno un sacco di esempi su internet per scambiare i byte in giro.

domanda successiva:

chiedo solo perché la mia dimensione del file è 3 invece di 196662

Ciò è dovuto a problemi di allineamento memoria. 196662 sono i byte 36 00 03 00 e 3 sono i byte 03 00 00 00. La maggior parte dei sistemi richiede tipi come int ecc. Per non essere suddivisi su più memorie words. Quindi, intuitivamente pensate che la vostra struct è disposto memoria im come:

      Offset 
short magic_number;  00 - 01 
int file_size;   02 - 05 
short reserved_bytes[2]; 06 - 09 
int data_offset;   0A - 0D 

ma su un sistema a 32 bit che significa files_size ha 2 byte nella stessa word come magic_number e due byte nel prossimo word. La maggior parte dei compilatori non sta in piedi per questo, quindi il modo in cui la struttura si articola in memoria è in realtà come:

short magic_number;  00 - 01 
<<unused padding>>  02 - 03 
int file_size;   04 - 07 
short reserved_bytes[2]; 08 - 0B 
int data_offset;   0C - 0F 

Così quando si legge il vostro flusso di byte nel 36 00 sta alla tua area padding che lascia il tuo file_size come ottenendo il 03 00 00 00. Ora se hai usato fwrite per creare questi dati avrebbe dovuto essere OK dato che i byte di riempimento sarebbero stati scritti. Ma se il tuo input sarà sempre nel formato che hai specificato, non è appropriato leggere l'intera struttura come se fosse una sola. Invece dovrai leggere ciascuno degli elementi individualmente.

+0

Mi dispiace, colpisci il salvataggio troppo presto. Tutti lì ora – Sodved

+0

+1 per la demo, anche se sarebbe bello rendere esplicito l'assunto little-endian. – zwol

+0

Questo ha effetto solo su 'short'?Chiedo solo perché la dimensione del mio file è 3 anziché 196662 –

0

Scrivere una struttura in un file è altamente non-portatile - è più sicuro non provare a farlo affatto. Usare una struct come questa è garantito per funzionare solo se a) la struct è sia scritta sia letta come struct (mai una sequenza di byte) eb) è sempre scritta e letta sullo stesso (tipo di) macchina. Non ci sono solo problemi "endian" con diverse CPU (che è quello che sembra che tu abbia mai incontrato), ci sono anche problemi di "allineamento". Diverse implementazioni hardware hanno regole diverse sul collocamento degli interi solo su limiti di 2 byte o anche di 4 byte o anche di 8 byte. Il compilatore è pienamente consapevole di tutto ciò e inserisce byte di riempimento nascosti nella struttura in modo che funzioni sempre correttamente. Ma come risultato dei byte di padding nascosti, non è del tutto sicuro assumere che i byte di una struct siano disposti in memoria come credi che siano. Se sei molto fortunato, lavori su un computer che usa l'ordine byte big-endian e non ha restrizioni di allineamento, così puoi porre le strutture direttamente sui file e farlo funzionare. Ma probabilmente non sei così fortunato - certamente i programmi che devono essere "portabili" a macchine diverse devono evitare di provare a porre le strutture direttamente su qualsiasi parte di qualsiasi file.

+0

grazie per aver condiviso la tua conoscenza. questo ha senso e cambierò il codice in futuro se scelgo di renderlo più portabile. –

+0

Blender 3d basa tutto il suo fileformato sulle strutture di lettura/scrittura dei file, gestendo anche i puntatori, l'endian e la conversione 32/64 bit. Non banale, ma non direi "non farlo affatto" – ideasman42

+0

@ ideasman42 Non sono completamente d'accordo. La corretta lettura/scrittura di strutture non è banale e facile da sbagliare in modi specifici specifici della piattaforma (come non essere in grado di condividere file tra macchine). Scrivere una piattaforma agnostica per leggere/scrivere manualmente i campi è banale e difficile da sbagliare, e per non parlare del fatto che funzionerà ovunque o da nessuna parte. Leggere e scrivere correttamente le strutture non è poi così difficile, ma è certamente più difficile senza alcun beneficio. – Kevin