2009-05-13 16 views
15

Mam plik binarny, który został utworzony na komputerze unix. To tylko kilka zapisów zapisanych jedna po drugiej. Rekord jest zdefiniowany coś takiego:Jak przekonwertować strukturę big-endian do little-endian-struct?

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

Próbuję dowiedzieć się, w jaki sposób czytać i interpretować te dane na komputerze z systemem Windows. Mam coś takiego:

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

RECORD r; 

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

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

Dostaję pęczek danych, ale to nie dane, których się spodziewam. Podejrzewam, że mój problem dotyczy endianowej różnicy maszyn, więc przyszedłem o to zapytać.

Rozumiem, że wiele bajtów będzie przechowywanych w little-endian w oknach i big-endian w środowisku uniksowym, a ja to rozumiem. Dla dwóch bajtów 0x1234 w systemie Windows będzie 0x3412 w systemie unix.

Czy endianość wpływa na porządek bajtów struktury jako całości lub każdego pojedynczego elementu struktury? Jakie podejście powinienem podjąć, aby przekonwertować strukturę utworzoną w systemie unix na taką, która ma te same dane w systemie Windows? Wszystkie linki, które są bardziej szczegółowe niż kolejność bajtów w kilku bajtach, również byłyby świetne!

+1

Nie pytałeś o nie, ale jeszcze jedną rzeczą do rozważenia przy pracy z tego rodzaju starszym kodem są bitfields. Kolejność pakowania bitoftów może być zarówno zależna od kompilatora, jak i platformy i niezwiązana z endianowością procesora. – Dan

Odpowiedz

12

Oprócz endianina, należy pamiętać o różnicach między dwoma platformami. W szczególności, jeśli macie tablice char nieparzystej długości i 16 bitowe wartości, możecie znaleźć różne liczby bajtów padów między niektórymi elementami.

Edycja: jeśli struktura została napisana bez opakowania, powinna być dość prosta. Coś jak ten (nietestowanego) Kod powinien wykonać zadanie:

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

Potem, kiedy już załadowany struct, po prostu zamienić każdy element:

SwapEndian(r.foo); 
SwapEndian(r.bar); 
SwapEndian(r.baz); 
+0

Mam określony pakiet #pragma pack (push, 1). – scottm

+0

@Scotty, to ci nie pomoże, jeśli dane, które już czytasz, zawierają luźne bajty. FWIW, to naprawdę nie powinno się zdarzyć, chyba że twórca programu wypisuje pełne struktury, co jest po prostu złe. Struktury powinny zawsze być wypisywane polem po polu - w sytuacjach dokładnie takich jak to. – Duck

+0

@Duck, mam źródło definicji struktury (ale nie do odczytu lub zapisu) i ma również pakiet = 1. – scottm

3

Wpływa na każdy członek niezależnie, a nie cały struct. Nie ma też wpływu na tablice. Na przykład, po prostu tworzy bajty w plikach int s zapisanych w odwrotnej kolejności.

PS. Powiedział, że może być maszyna z dziwną endianness. To, co właśnie powiedziałem, dotyczy najczęściej używanych maszyn (x86, ARM, PowerPC, SPARC).

+0

"Nie ma to również wpływu na tablice.": Ale wpływa na członków tablic, jeśli są to dane liczbowe lub znaki o rozmiarze> 1 bajta! – mmmmmmmm

+1

@rstevens: Tak, absolutnie. Mam na myśli to, że nie wpływa to na kolejność elementów w tablicy.Każdy członek jest oczywiście traktowany jak pojedyncza zmienna. –

10

W rzeczywistości endianness jest własnością podstawowego sprzętu, a nie systemu operacyjnego.

Najlepszym rozwiązaniem jest przekonwertowanie do standardu podczas zapisywania danych - Google for "sieć kolejność bajtów" i powinieneś znaleźć metody, aby to zrobić.

Edycja: Oto link: http://www.gnu.org/software/hello/manual/libc/Byte-Order.html

+1

Nie mam możliwości decydowania o tym, jak pisać dane, proces ten istnieje od 10 lat i się nie zmienia. – scottm

+1

W takim przypadku musisz odkryć dokładny mechanizm, który został użyty i napisać własne procedury do konwersji (lub znaleźć je w Internecie). Należy jednak pamiętać, że podczas gdy scenarzysta "się nie zmienia", lepiej nigdy nie przenosić się do innej architektury, bo inaczej się to zmieni. – kdgregory

1

Trzeba skorygować kolejność bajtów każdego członka więcej niż jeden bajt, indywidualnie. Ciągi nie muszą być konwertowane (fooword i barword), ponieważ mogą być postrzegane jako ciągi bajtów.

Musisz jednak zająć się innym problemem: aligmenentem członków w twojej strukturze. Zasadniczo musisz sprawdzić, czy sizeof (RECORD) jest taki sam na kodach unix i windows. Kompilatory zwykle dostarczają pragmom zdefiniowania pożądanego stopnia dopasowania (na przykład #pragma pack).

1

trzeba także wziąć pod uwagę różnice pomiędzy wyrównania dwa kompilatory.Każdy kompilator może wstawiać dopełnienie między elementami w strukturze najlepiej pasującej do architektury. Tak naprawdę trzeba wiedzieć:

  • Jak prog UNIX zapisuje do pliku
  • Jeśli jest to binarny kopia obiektu dokładny układ struktury.
  • Jeśli jest to kopia binarna, to co jest endingiem architektury źródłowej.

Oto dlaczego większość programów (które widziałem (które muszą być neutralne dla platformy)) przekształca dane w postaci strumienia tekstowego, który może być łatwo odczytany przez standardowe iostreams.

0

Coś jak to powinno działać:

#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; 
} 
1

Chciałbym zaimplementować metodę SwapBytes dla każdego typu danych, który wymaga zamiana, tak:

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

Potem dodać funkcję do struktury który wymaga wymiany, na przykład:

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

Następnie można zmodyfikować kod, który czyta (lub pisze) t ma strukturę podobną do tej:

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; 

Aby obsługiwać różne platformy, wystarczy mieć specyficzną dla platformy implementację każdego przeciążenia ByteSwap.

4

Nie czytaj bezpośrednio w struct z pliku! Pakowanie może być inne, trzeba skrzypce z pakietem pragma lub podobnymi kompilatorami. Zbyt niewiarygodne. Wielu programistów ucieka od tego, ponieważ ich kod nie jest skompilowany w wielu architekturach i systemach, ale to nie znaczy, że jest to w porządku!

Dobrym alternatywnym podejściem jest odczytanie nagłówka, cokolwiek, do bufora i parsowanie od trzech, aby uniknąć narzutu we/wy w operacjach atomowych, takich jak czytanie 32-bitowej liczby całkowitej bez znaku!

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; 

Deklaracja parse_uint32 będzie wyglądać następująco:

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

To bardzo proste abstrakcji, to nie ma żadnych dodatkowych kosztów w praktyce aktualizować wskaźnik, a także:

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

Późniejszy formularz umożliwia czystszy kod do analizowania bufora; wskaźnik jest automatycznie aktualizowany podczas analizowania z wejścia.

Podobnie mogłoby memcpy mieć pomocnika, coś jak:

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

Piękno tego rodzaju rozwiązania jest to, że można mieć nazw „LITTLE_ENDIAN” i „big_endian”, to może to robić w Kod:

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

łatwo przełączać kolejność bajtów dla tego samego kodu, choć rzadko potrzebnych funkcji .. formatów plików zwykle mają ustaloną kolejność bajtów w każdym razie.

NIE zamieniaj tego na klasy za pomocą metod wirtualnych; po prostu dodać narzut, ale czuć się swobodnie, jeśli tak pochylona:

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

Przedmiotem czytelnik będzie oczywiście być tylko cienka owijka wokół wskaźnika. Parametr wielkości będzie służył do sprawdzania błędów, jeśli jest. Niezupełnie obowiązkowe dla interfejsu per se.

Zauważ, jak wybór tu nie-endianess został zrobiony w CZASIE KOMPILACJI (ponieważ tworzymy obiekt little_endian_reader), więc wywołujemy metodę wirtualną narzut bez szczególnego powodu, więc nie podjąłbym tego podejścia. ;-)

Na tym etapie nie ma prawdziwego powodu, aby zachować strukturę plików w niezmienionej postaci, można uporządkować dane według własnych upodobań i niekoniecznie przeczytać je w jakiejkolwiek konkretnej strukturze; w końcu to tylko dane. Kiedy czytasz pliki takie jak obrazy, tak naprawdę nie potrzebujesz nagłówka dookoła ... powinieneś mieć swój kontener obrazu, który jest taki sam dla wszystkich typów plików, więc kod do odczytania określonego formatu powinien po prostu odczytać plik, zinterpretować i sformatować ponownie dane & przechowaj ładunek. =)

Mam na myśli, czy to wygląda skomplikowanie?

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

Kod może wyglądać tak ładnie i być naprawdę niewielki! Jeśli kolejność bajtów jest taka sama dla pliku i architektury kod jest kompilowany do The innerloop może wyglądać następująco:

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

To może być nielegalne w niektórych architekturach, tak że optymalizacja może być zły pomysł i używać wolniej, lecz bardziej solidne podejście:

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

na x 86, który może kompilacji do bswap lub mov, która jest stosunkowo niska napowietrznych jeśli sposób wstawiane; kompilator wstawił węzeł "przenieś" do kodu pośredniego, nic ponadto, co jest dość wydajne. Jeśli wyrównanie jest problemem, cała sekwencja read-shift-lub może zostać wygenerowana, outch, ale nadal nie jest zbyt shabby. Funkcja porównania może pozwolić na optymalizację, jeśli przetestuje adres LSB i sprawdzi, czy można użyć szybkiej lub wolnej wersji analizy. Ale oznaczałoby to karę za test w każdym czytaniu. Może nie był wart wysiłku.

O, prawda, czytamy HEADERS i takie tam, nie sądzę, że jest to wąskie gardło w zbyt wielu aplikacjach. Jeśli jakiś kodek robi naprawdę WILGOTNY PUNKT, znowu odczytanie bufora tymczasowego i dekodowanie z niego jest dobrze przygotowane. Ta sama zasada. Nikt nie czyta bajt-w-czasie z pliku podczas przetwarzania dużej ilości danych. Tak naprawdę, widziałem ten rodzaj kodu bardzo często, a zwykła odpowiedź "dlaczego to robisz" jest taka, że ​​systemy plików blokują odczyty i że bajty pochodzą z pamięci, tak czy inaczej, ale przechodzą przez głęboki stos wywołań co jest dużym obciążeniem dla uzyskania kilku bajtów!

Jeszcze raz napisz kod parsera i użyj zillion razy -> epicka wygrana.

Odczytanie bezpośrednio do struktury z pliku: NIE ROZPOCZNIJ!