serializacja/deserializacja a endianowość

0

Czy serializacja i deserializacja pól struktury do pojedynczych bajtów wystarczy by działało nam wysyłanie danych pomiędzy urządzeniami w których sa różne architektury i różne endianowości czy może potrzeba czegoś jeszcze extra by nasz kod był odporny na wszelkiego rodzaje kombinacje maszyn pomiedzy ktorymi dane mogą być wysyłane/odbierane?
PC arch64bit Big-Endia <---> PC arch32 LittleEndian
PC arch32 LittleEndian <-----> PC arch32 LittleEndian
Tam wiadomo jeszcze przed wysłaniem trzeba użyć funkcji htonl/s a po stronie odbiorczej nthol/s.
Jak serializuję (o ile nie korzystamy z cereala) to robię:

struct A {
    int x;
    ...
};

void serialize(A *wsk)
{
      byte[0] = wsk->x & 0xFF;
      byte[1] = (wsk->x & 0xFF00) >> 8;
      byte[2] = (wsk->x & 0xFF0000) >> 16;
      byte[3] = (wsk->x & 0xFF000000) >> 24;
}

void deserialize(unit8_t byte[], int size)
{
    wsk->x = byte[0];
    wsk->x |= byte[1] << 8;
    wsk->x |= byte[2] << 16;
    wsk->x |= byte[3] << 24;
}

Ale to chyba powinno wystarczyć, bo jak dam nawet w kodzie zapis:
int x = 0x12345678; //w ramce fizycznej/stronie wirtualnej przestrzeni adresowej procesu wartość jako Little albo BigEndia storowana
....
int z = x; //pobranie komórki pamięci ramki fizycznej i wrzucenie wartości do nowej
to jakby w kodzie zawsze jest endianowość BigEndian. Podejrzewam, że kompilator musi wiedzieć o endianowości i tu jest jakby ta konwersja ukryta, że dla programisty zawsze ten x,z to ma BigEndianowość.

0

Wysyłaniem, ale przez co? Bo wysyłać można na rożne sposoby.

0
Tenonymous napisał(a):

Wysyłaniem, ale przez co? Bo wysyłać można na rożne sposoby.

po udp albo tcp.
Jeszcze też zastanawiam się nad takim przypadkiem. Mamy ten sam program. I kompilujemy go na 2 różne architektury o Little i Big Endianowości. Ktoś ten program uruchamia na kompie A o BigEndianowości i ten program ma funckjonalność zapisu do pliku i zapisuje nim dane, niech będzie że do dwóch plików: pierwszy w formie binarnej i drugi plik tekstowej.
Następnie bierze plik na pendriva i przenosi go na kompa B o LittleEndianowści i robi odczyt obu plików programem (powiedzmy że ma też funkcję odczytu)
Czy myślicie że te funkcje standardowe getline, read, write czy wszlekiego rodzaju ifstream itp ogarną to? I jak ktoś zrobi zapis do stringa po użyciu API do odczytu plików to będzie miał ciąg znaków taki jak w pliku? Czy tu też trzeba kombinować z serializacją i deserializacją przed zapisem i po odczycie z pliku?

0

Końcówkowość ma znaczenie, gdy serializujesz liczby do ciągu bajtów. Trzymaj się jednej konwencji, np. liczby zapisane jako cienkokońcówkowe (albo właśnie grubokońcówkowe), i napisz funkcje, które odtwarzają liczbę z takiego zapisu, niezależnie od platformy (albo użyj htons htonl ntohs ntohl itp. - wtedy dostaniejsz grubokońcówkowe).
Może powiedz w czym dokładnie widzisz problem?

0

Nie widzę problemu, tylko pytam o potwierdzenie czy dla przesylania danych po udp/tcp wystarczy tylko serializacja i deserializacja?
Problem mam natomiast z plikiem.
Ja generalnie kompletnie nie rozumiem jak to jest, że na platformie LittleEndian mam coś takiego:

  • w kodzie zmienna x=0x12345678
  • jak dam coutem na konsole to mam też 0x12345678
  • w pamięci mam 0x78 0x56 0x34 0x12(*)
  • w pliku tekstowym jak sobie otworzę to mam 0x12345678

Moje rozumie jest takie, że jak mam wirtualną przestrzeń adresową programu to każda strona tej przestrzeni musi być odwzorowana w ramce fizycznej w ramie. Czyli jak procesor na stosie umieści zmienną x 0x12345678 to ta wartość zostanie zapisana w pamięci w taki sposób (*). Następnie mamy zapis do pliku i czemu w pliku nie ma wartości takiej jak w pamięci a jest taka jak w kodzie? Może to jest tak że tak naprawdę w tym pliku w RAMie, na dysku ona dalej jest jako LittleEndian ale program do odczytu jest tak napisany by zawsze zwracać użytkownikowi by widział BigEndian. Czyli programiści edytora tekstu chyba wykrywają endianowość jaka jest w systemie i odpowiednio konwertują pamięć RAMU, dysku twardego aby użytkownik zawsze widział BigEndian.
Niemniej jednak jakby przenieść taki plik na system BigEndian i ktoś by zrobił odczyt tego pliku sstreamem czy nawet jakimś istniejącym edytorem to użytkownik zobaczyłby chyba inny tekst.
Tak czy siak dużo zamieszania ta endianowość wprowadza, na codzień o niej sie nie myśli, ale jak pójdziesz na rozmowę kwalifikacyjną to spodziewaj się takich głupich pytań.:)

0

@fvg po pierwsze to pytanie gdzie w pamięci. Bo stos rośnie odwrotnie niż sterta ;) Dane umieszczone na stercie są ułożone w dobrym kierunku, ale na stosie są odwrotnie (tzn jak zrobisz na stosie char* x = "abcd"; to fizycznie w pamięci będzie dcba, ale gdyby to było na stercie to byłoby abcd :)

1
Shalom napisał(a):

@fvg po pierwsze to pytanie gdzie w pamięci. Bo stos rośnie odwrotnie niż sterta ;) Dane umieszczone na stercie są ułożone w dobrym kierunku, ale na stosie są odwrotnie (tzn jak zrobisz na stosie char* x = "abcd"; to fizycznie w pamięci będzie dcba, ale gdyby to było na stercie to byłoby abcd :)

gdyby każdy znak był kładziony na stos osobną instrukcją push to by ułożenie w pamięci było faktycznie odwrócone, no ale co z takim strcmp, które z założenia leci po adresach w górę? Przecież to nie miałoby sensu.

0
fvg napisał(a):

Nie widzę problemu, tylko pytam o potwierdzenie czy dla przesylania danych po udp/tcp wystarczy tylko serializacja i deserializacja?
Problem mam natomiast z plikiem.
Ja generalnie kompletnie nie rozumiem jak to jest, że na platformie LittleEndian mam coś takiego:

  • w kodzie zmienna x=0x12345678
  • jak dam coutem na konsole to mam też 0x12345678
  • w pamięci mam 0x78 0x56 0x34 0x12(*)
  • w pliku tekstowym jak sobie otworzę to mam 0x12345678

w kodzie, czyli pliku tekstowym masz taki ciąg [0,x,1,2,3,4,5,6,7,8], czyli "BigEndian", ale to jest tylko sekwencja znaków czytana od lewa do prawa jak to w Europie przystało. Tutaj standardem jest BigEndian bo to wynika z kolejności wypisywania znaków z pliku do terminala. Kompilator przetworzy ją na int i umieści np. w EAX, który jako rejestr 32bit nie ma endianowości. Zaczyna się ciekawie gdy zajdzie potrzeba zapisania do pamięci RAM, wtedy najmniejszy bajt rejestru EAX będzie zapisany w komórce o niższym adresie w architekturze Little-Endian. Na Big-Endian odwrotnie.

Problemy dopiero są gdy liczby zapisuje się do plików binarnych czyli robi zrzut pamięci do pliku, bajt po bajcie, albo bajty przesyła się po sieci. Formaty tekstowe nigdy nie będą miały takiego problemu, bo terminal pisze po ekranie od lewa do prawa, od góry do dołu.

Moje rozumie jest takie, że jak mam wirtualną przestrzeń adresową programu to każda strona tej przestrzeni musi być odwzorowana w ramce fizycznej w ramie. Czyli jak procesor na stosie umieści zmienną x 0x12345678 to ta wartość zostanie zapisana w pamięci w taki sposób (*).

Czy to będzie stos czy sterta zapis rejestru do pamięci będzie miał taki sam endian.

Następnie mamy zapis do pliku i czemu w pliku nie ma wartości takiej jak w pamięci a jest taka jak w kodzie? Może to jest tak że tak naprawdę w tym pliku w RAMie, na dysku ona dalej jest jako LittleEndian ale program do odczytu jest tak napisany by zawsze zwracać użytkownikowi by widział BigEndian. Czyli programiści edytora tekstu chyba wykrywają endianowość jaka jest w systemie i odpowiednio konwertują pamięć RAMU, dysku twardego aby użytkownik zawsze widział BigEndian.
Niemniej jednak jakby przenieść taki plik na system BigEndian i ktoś by zrobił odczyt tego pliku sstreamem czy nawet jakimś istniejącym edytorem to użytkownik zobaczyłby chyba inny tekst.
Tak czy siak dużo zamieszania ta endianowość wprowadza, na codzień o niej sie nie myśli, ale jak pójdziesz na rozmowę kwalifikacyjną to spodziewaj się takich głupich pytań.:)

Z endianowością mogą być problemy z plikami i strumieniami binarnymi, gdzie 4 bajty odebrane w czasie i umieszczone w buforze w pamięci o adresach rosnących. Wtedy trzeba tą czwórkę zebrać do kupy i umieścić w większym rejestrze. Tutaj się trzeba pilnować, czyli wiedzieć jaki endian jest w pliku lub strumieniu binarnym.

Z tekstem zero problemu bo liczby zapisujemy od najbardziej znaczącej cyfry bo tak się ludzie przyzwyczaili

0
Shalom napisał(a):

@fvg po pierwsze to pytanie gdzie w pamięci. Bo stos rośnie odwrotnie niż sterta ;) Dane umieszczone na stercie są ułożone w dobrym kierunku, ale na stosie są odwrotnie (tzn jak zrobisz na stosie char* x = "abcd"; to fizycznie w pamięci będzie dcba, ale gdyby to było na stercie to byłoby abcd :)

Czy nie pomyliłeś przypadku

char a = 'a', b = 'b', c = 'c', d = 'd';

z

char x[] = "abcd"

?

5

Po co odkrywać koło na nowo?
Najprostsze rozwiązanie użyj gotowego!
protobuf jest bardzo przyjemny, a co ważniejsze działa w zasadzie z dowolnym językiem.

1
jvoytech napisał(a):
Shalom napisał(a):

@fvg po pierwsze to pytanie gdzie w pamięci. Bo stos rośnie odwrotnie niż sterta ;) Dane umieszczone na stercie są ułożone w dobrym kierunku, ale na stosie są odwrotnie (tzn jak zrobisz na stosie char* x = "abcd"; to fizycznie w pamięci będzie dcba, ale gdyby to było na stercie to byłoby abcd :)

gdyby każdy znak był kładziony na stos osobną instrukcją push to by ułożenie w pamięci było faktycznie odwrócone, no ale co z takim strcmp, które z założenia leci po adresach w górę? Przecież to nie miałoby sensu.

Dokładnie. Stos i sterta może i rosną odwrotnie, ale adresowanie bajtów w strukturze/tablicy/etc w c i c++ jest w tą samą stronę. Więc jedyna różnica gdzie zaczyna się nasz odcinek pamięci.

Poza tym sterta nie musi być ciągła, to jest uproszczony model ... Nie zawsze jest powiększana przez brk, całe strony mogą być przydzielone lub zabrane niezależnie (czyli powstanie luka po zabraniu ich...), np poprzez mmap w unixach.

1
fvg napisał(a):

. Ktoś ten program uruchamia na kompie A o BigEndianowości i ten program ma funckjonalność zapisu do pliku i zapisuje nim dane, niech będzie że do dwóch plików: pierwszy w formie binarnej i drugi plik tekstowej.
Następnie bierze plik na pendriva i przenosi go na kompa B o LittleEndianowści i robi odczyt obu plików programem (powiedzmy że ma też funkcję odczytu)
Czy myślicie że te funkcje standardowe getline, read, write czy wszlekiego rodzaju ifstream itp ogarną to? I jak ktoś zrobi zapis do stringa po użyciu API do odczytu plików to będzie miał ciąg znaków taki jak w pliku? Czy tu też trzeba kombinować z serializacją i deserializacją przed zapisem i po odczycie z pliku?

W przypadku formatów tekstowych, jeśli masz kodowanie jeno bajtowe, problemu z endiną nie ma (np UTF-8).
Jeśli plik tekstowy jest w kodowaniu wielo bajtowym (USC-2, UTF-16 UTF-32 itp) to taki plik powinien się zacząć od znaku BOM (Byte Order Mark), a oprogramowanie wczytujące powinno to uwzględniać.
W standardowym C++ obsługa tego to jest koszmar, a z MSVC C++ to pojedynek z w Freddy Krueger'em.

Binarne pliki to już inna historia, naprawdę polecam protobuf.

2
Shalom napisał(a):

@fvg po pierwsze to pytanie gdzie w pamięci. Bo stos rośnie odwrotnie niż sterta ;) Dane umieszczone na stercie są ułożone w dobrym kierunku, ale na stosie są odwrotnie (tzn jak zrobisz na stosie char* x = "abcd"; to fizycznie w pamięci będzie dcba, ale gdyby to było na stercie to byłoby abcd :)

Coś mi tu mocno nie pasuje w tym stwierdzeniu - i nie chodzi mi nawet o to, że wskaźnik wskazuje na dane z data albo innego bcc. Przecież &x[0] == &x[1]-1 niezależnie gdzie ten string się znajduje. Tak samo liczby nia stają się nagle big endian tylko dlatego, że są w jakimś rejonie pamięci.

1 użytkowników online, w tym zalogowanych: 0, gości: 1