Hvordan kan jeg konvertere en big-endian struct til en liten endian-struct?

stemmer
16

Jeg har en binær fil som ble opprettet på en unix maskin. Det er bare en haug med poster skrevet en etter en. Rekorden er definert noe sånt som dette:

struct RECORD {
  UINT32 foo;
  UINT32 bar;
  CHAR fooword[11];
  CHAR barword[11];
  UNIT16 baz;
}

Jeg prøver å finne ut hvordan jeg skulle lese og tolke disse dataene på en Windows-maskin. Jeg har noe sånt som dette:

fstream f;
f.open(file.bin, ios::in | ios::binary);

RECORD r;

f.read((char*)&detail, sizeof(RECORD));

cout << fooword =  << r.fooword << endl;

Jeg får en haug med data, men det er ikke data jeg forventer. Jeg mistenker at min problemet har å gjøre med endian forskjell på maskinene, så jeg har kommet for å be om det.

Jeg forstår at flere bytes vil bli lagret i lite endian på vinduer og big-endian i et unix miljø, og jeg får det. For to bytes, vil 0x1234 på vinduer være 0x3412 på en unix system.

Har endianness påvirke byte rekkefølgen av struct som helhet, eller for hvert enkelt medlem av struct? Hva tilnærminger vil jeg ta for å konvertere en struct opprettet på en unix system til en som har de samme dataene på et Windows-system? Noen linker som er mer i dybden enn byte orden et par bytes ville være stor, også!

Publisert på 13/05/2009 klokken 18:19
kilden bruker
På andre språk...                            


8 svar

stemmer
12

Samt endian, må du være klar over padding forskjeller mellom de to plattformene. Spesielt hvis du har odde lengde char arrays og 16 bit-verdier, kan du godt finne forskjellige antall pad byte mellom noen elementer.

Edit: hvis strukturen ble skrevet ut uten pakking, så det bør være ganske grei. Noe sånt som dette (testet) koden skal gjøre jobben:

// Functions to swap the endian of 16 and 32 bit values

inline void SwapEndian(UINT16 &val)
{
    val = (val<<8) | (val>>8);
}

inline void SwapEndian(UINT32 &val)
{
    val = (val<<24) | ((val<<8) & 0x00ff0000) |
          ((val>>8) & 0x0000ff00) | (val>>24);
}

Deretter, når du har lastet den struct, bare bytte hvert element:

SwapEndian(r.foo);
SwapEndian(r.bar);
SwapEndian(r.baz);
Svarte 13/05/2009 kl. 18:27
kilden bruker

stemmer
10

Egentlig er endianness en egenskap av den underliggende maskinvaren, ikke OS.

Den beste løsningen er å konvertere til en standard når du skriver data - Google etter "nettverk byte order", og du bør finne metoder for å gjøre dette.

Edit: her er linken: http://www.gnu.org/software/hello/manual/libc/Byte-Order.html

Svarte 13/05/2009 kl. 18:22
kilden bruker

stemmer
5

Ikke leses direkte inn i struct fra en fil! Emballasjen kan være annerledes, må du fikle med pragma pakke eller lignende kompilatoren bestemte konstruksjoner. Altfor upålitelig. Mange programmerere komme unna med dette siden koden ikke er samlet i stort antall arkitekturer og systemer, men det betyr ikke at det er OK ting å gjøre!

Et godt alternativ tilnærming er å lese den øverste del, uansett, inn i en buffer og analysere fra tre for å unngå at I / O-mengden i atom operasjoner som å lese en usignert 32 bits heltall!

char buffer[32];
char* temp = buffer;  

f.read(buffer, 32);  

RECORD rec;
rec.foo = parse_uint32(temp); temp += 4;
rec.bar = parse_uint32(temp); temp += 4;
memcpy(&rec.fooword, temp, 11); temp += 11;
memcpy(%red.barword, temp, 11); temp += 11;
rec.baz = parse_uint16(temp); temp += 2;

Erklæringen fra parse_uint32 ville se slik ut:

uint32 parse_uint32(char* buffer)
{
  uint32 x;
  // ...
  return x;
}

Dette er en veldig enkel abstraksjon, det koster ikke noe ekstra i praksis å oppdatere pekeren også:

uint32 parse_uint32(char*& buffer)
{
  uint32 x;
  // ...
  buffer += 4;
  return x;
}

Den senere form muliggjør renere kode for analysering av buffer; pekeren oppdateres automatisk når du analysere fra inngangen.

Likeledes memcpy kunne ha en hjelper, noe sånt som:

void parse_copy(void* dest, char*& buffer, size_t size)
{
  memcpy(dest, buffer, size);
  buffer += size;
}

Det fine med en slik ordning er at du kan ha namespace "little_endian" og "big_endian", så kan du gjøre dette i din kode:

using little_endian;
// do your parsing for little_endian input stream here..

Lett å slå endianess for den samme koden, men sjelden nødvendig har .. fil-formater som regel en fast endianess uansett.

IKKE abstrakt denne inn i klassen med virtuelle metoder; ville bare legge overhead, men gjerne hvis så tilbøyelig:

little_endian_reader reader(data, size);
uint32 x = reader.read_uint32();
uint32 y = reader.read_uint32();

Leseren gjenstand vil selvfølgelig bare være en tynn omhylling rundt pekeren. Størrelsesparameteren kan være for feilkontroll, hvis noen. Ikke egentlig obligatorisk for grensesnittet per se.

Legg merke til hvordan valg av endianess her ble gjort på SAMLE TIME (siden vi skape little_endian_reader objekt), så vi påkalle den virtuelle metoden overhead for ingen spesielt god grunn, så jeg ville ikke gå med denne tilnærmingen. ;-)

På dette stadiet er det ingen reell grunn til å holde "filformatet struct" rundt som den er, kan du organisere dataene til din smak og ikke nødvendigvis lese det inn i noen bestemt struct i det hele tatt; tross alt, det er bare data. Når du leser filer som bilder, trenger du egentlig ikke trenger header rundt .. du bør ha ditt bilde container som er lik for alle filtyper, så koden for å lese et bestemt format skal bare lese filen, tolke og formatere data og lagre nyttelast. =)

Jeg mener, ser dette komplisert?

uint32 xsize = buffer.read<uint32>();
uint32 ysize = buffer.read<uint32>();
float aspect = buffer.read<float>();    

Koden kan se det hyggelig, og være en veldig lav overhead! Hvis endianess er samme for fil og arkitektur koden er kompilert for, kan innerloop se slik ut:

uint32 value = *reinterpret_cast<uint32*>)(ptr); ptr += 4;
return value;

Det kan være ulovlig på noen arkitekturer, slik at optimalisering kan være en dårlig idé, og bruke tregere, men mer robust tilnærming:

uint32 value = ptr[0] | (static_cast<uint32>(ptr[1]) << 8) | ...; ptr += 4;
return value;

På en x86 som kan samle inn bswap eller mov, som er rimelig lav-overhead dersom fremgangsmåten inlined; kompilatoren vil sette inn "flytte" node inn i mellomkoden, ikke noe annet, noe som er ganske effektivt. Hvis justeringen er et problem full lese-shift-eller sekvens kan bli generert, outch, men likevel ikke altfor dårlig. Sammenligne-gren kan tillate optimalisering, hvis testadresse LSB og se om kan bruke rask eller langsom versjon av parsing. Men dette ville bety straff for testen i alle lese. Kanskje ikke verdt innsatsen.

Oh, ikke sant, vi leser overskrifter og sånt, jeg tror ikke det er en flaskehals i for mange applikasjoner. Hvis noen codec gjør noen virkelig TIGHT innerloop, igjen, leser i en midlertidig buffer og dekoding derfra er godt råd. Samme prinsipp .. ingen leser byte-på-tid fra fil ved behandling av et stort volum av data. Vel, faktisk, jeg har sett den slags kode svært ofte og vanlig svar til "hvorfor du gjør det" er at filsystemer gjøre blokk leser og at bytes kommer fra minnet likevel, sant, men de går gjennom en dyp samtale stack som er høy overhead for å få noen få byte!

Likevel, skriver parser koden en gang og bruk zillion ganger -> episk seier.

Lese direkte inn struct fra en fil: IKKE GJØR DET FOLKENS!

Svarte 29/06/2009 kl. 13:38
kilden bruker

stemmer
3

Det påvirker hvert medlem uavhengig, ikke hele struct. Dessuten vil det ikke påvirke ting som arrays. For eksempel, gjør det bare bytes i en ints som er lagret i omvendt rekkefølge.

PS. Når det er sagt, det kan være en maskin med rare endianness. Det jeg sa bare gjelder for de fleste brukte maskiner (x86, ARM, PowerPC, SPARC).

Svarte 13/05/2009 kl. 18:21
kilden bruker

stemmer
1

Jeg liker å implementere en SwapBytes metode for hver datatype som trenger å bytte, som dette:

inline u_int ByteSwap(u_int in)
{
    u_int out;
    char *indata = (char *)&in;
    char *outdata = (char *)&out;
    outdata[0] = indata[3] ;
    outdata[3] = indata[0] ;

    outdata[1] = indata[2] ;
    outdata[2] = indata[1] ;
    return out;
}

inline u_short ByteSwap(u_short in)
{
    u_short out;
    char *indata = (char *)&in;
    char *outdata = (char *)&out;
    outdata[0] = indata[1] ;
    outdata[1] = indata[0] ;
    return out;
}

Deretter legger jeg til en funksjon i den strukturen som trenger å bytte, som dette:

struct RECORD {
  UINT32 foo;
  UINT32 bar;
  CHAR fooword[11];
  CHAR barword[11];
  UNIT16 baz;
  void SwapBytes()
  {
    foo = ByteSwap(foo);
    bar = ByteSwap(bar);
    baz = ByteSwap(baz);
  }
}

Deretter kan du endre koden som leser (eller skriver) strukturen som dette:

fstream f;
f.open("file.bin", ios::in | ios::binary);

RECORD r;

f.read((char*)&detail, sizeof(RECORD));
r.SwapBytes();

cout << "fooword = " << r.fooword << endl;

For å støtte ulike plattformer du trenger bare å ha en plattformspesifikk implementering av hver ByteSwap overbelastning.

Svarte 13/05/2009 kl. 19:02
kilden bruker

stemmer
1

Du må også vurdere justerings forskjeller mellom de to kompilatorer. Hver kompilatoren blir tillatt å sette inn polstring mellom medlemmer i en struktur som de best passer arkitekturen. Så du virkelig trenger å vite:

  • Hvordan UNIX prog skriver til filen
  • Hvis det er en binær kopi av objektet nøyaktig utformingen av strukturen.
  • Hvis det er en binær kopi hva endian-ness av kilden arkitektur.

Dette er grunnen til de fleste programmer (som jeg har sett (som må være plattformnøytrale)) serial dataene som en tekst bekk som lett kan leses av standard iostreams.

Svarte 13/05/2009 kl. 18:31
kilden bruker

stemmer
1

Du må korrigere endianess av hvert medlem av mer enn én byte, individuelt. Strenger trenger ikke å bli omdannet (fooword og barword), som de kan bli sett på som sekvenser av bytes.

Men du må ta vare på et annet problem: aligmenent av medlemmene i struct. I utgangspunktet må du sjekke om sizeof (RECORD) er den samme på begge Unix og Windows-kode. Kompilatorer gir vanligvis pragmas å definere aligment du ønsker (for eksempel #pragma pack).

Svarte 13/05/2009 kl. 18:27
kilden bruker

stemmer
0

Noe sånt som dette skal fungere:

#include <algorithm>

struct RECORD {
    UINT32 foo;
    UINT32 bar;
    CHAR fooword[11];
    CHAR barword[11];
    UINT16 baz;
}

void ReverseBytes( void *start, int size )
{
    char *beg = start;
    char *end = beg + size;

    std::reverse( beg, end );
}

int main() {
    fstream f;
    f.open( "file.bin", ios::in | ios::binary );

    // for each entry {
    RECORD r;
    f.read( (char *)&r, sizeof( RECORD ) );
    ReverseBytes( r.foo, sizeof( UINT32 ) );
    ReverseBytes( r.bar, sizeof( UINT32 ) );
    ReverseBytes( r.baz, sizeof( UINT16 )
    // }

    return 0;
}
Svarte 13/05/2009 kl. 18:35
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more