serializacja/deserializacja a endianowość

serializacja/deserializacja a endianowość
FV
  • Rejestracja:prawie 6 lat
  • Ostatnio:około 2 miesiące
  • Postów:96
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ę:

Kopiuj
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ść.

edytowany 2x, ostatnio: fvg
Tenonymous
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 8 godzin
  • Postów:425
0

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

FV
  • Rejestracja:prawie 6 lat
  • Ostatnio:około 2 miesiące
  • Postów:96
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?

edytowany 3x, ostatnio: fvg
enedil
  • Rejestracja:ponad 11 lat
  • Ostatnio:około 19 godzin
  • Postów:1027
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?

FV
  • Rejestracja:prawie 6 lat
  • Ostatnio:około 2 miesiące
  • Postów:96
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ń.:)

edytowany 2x, ostatnio: fvg
OG
No chyba że pracujesz w branży telekomunikacyjnej wtedy myślisz o niej codziennie, ale można użyć protocol buffers i wtedy zapominasz o takich historiach(i jeszcze paru innych jak np. zapewnianie kompatybilności wstecznej przy nowych wersjach serializowanych wiadomości).
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
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 :)


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
JV
  • Rejestracja:ponad 6 lat
  • Ostatnio:2 miesiące
  • Postów:242
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.

JV
  • Rejestracja:ponad 6 lat
  • Ostatnio:2 miesiące
  • Postów:242
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

enedil
  • Rejestracja:ponad 11 lat
  • Ostatnio:około 19 godzin
  • Postów:1027
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

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

z

Kopiuj
char x[] = "abcd"

?

MarekR22
Moderator C/C++
  • Rejestracja:około 17 lat
  • Ostatnio:4 minuty
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.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 1x, ostatnio: MarekR22
nalik
  • Rejestracja:około 9 lat
  • Ostatnio:prawie 2 lata
  • Postów:1039
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.

edytowany 4x, ostatnio: nalik
MarekR22
Moderator C/C++
  • Rejestracja:około 17 lat
  • Ostatnio:4 minuty
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.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:3 dni
  • Lokalizacja:Szczecin
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.


mwl4
@Shalom pomylił dwie rzeczy moim zdaniem. Bo stos faktycznie rośnie do tyłu, ale w obrębie całych obiektów. Czyli jeśli mamy funkcję i ona potrzebuje pamięci na obiekty to pamięć jest rezerwowana przez odejmowanie od esp/rsp. Natomiast stwierdzenie stos rośnie odwrotnie niż sterta to bzdura jakaś. Jeśli mamy tablicę char to kolejne jej elementy będą na kolejnych adresach w pamięci: https://godbolt.org/z/SGf3Xe
kq
To że stos rośnie do tyłu to wiem, ale nie to jest treścią posta ;​)
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)