Czy rzuty pomiędzy signed i unsigned int zachowują dokładny wzorzec bitowy zmiennej w pamięci?

Chcę przekazać 32-bitową, podpisaną liczbę całkowitą x przez gniazdo. Aby odbiorca wiedział, jakiej kolejności bajtów się spodziewać, wywołuję htonl(x) przed wysłaniem. htonl oczekuje jednak uint32_t i chcę być pewien, co się stanie, gdy rzucę moje int32_t na uint32_t.

int32_t x = something;
uint32_t u = (uint32_t) x;

Czy zawsze jest tak, że bajty w x i u będą dokładnie takie same? A co z oddaniem:

uint32_t u = something;
int32_t x = (int32_t) u;

Zdaję sobie sprawę, że wartości ujemne rzucane na duże wartości niepodpisane ale to nie ma znaczenia, ponieważ ja tylko rzucam się na drugi koniec. Jednak jeśli cast MES z rzeczywistymi bajtami, to nie mogę być pewien, że casting back zwróci tę samą wartość.

Author: Flash, 2013-10-21

3 answers

Ogólnie rzecz biorąc, odlewanie w C jest określone pod względem wartości, a nie wzorców bitowych - pierwsze zostaną zachowane( jeśli to możliwe), ale drugie niekoniecznie. W przypadku dwóch reprezentacji dopełniacza bez wypełnienia-co jest obowiązkowe dla stałych-z typami całkowitymi-rozróżnienie to nie ma znaczenia i rzut rzeczywiście będzie noop.

Ale nawet jeśli konwersja z signed do unsigned zmieniłaby wzorzec bitowy, konwersja z powrotem przywróciłaby wartość oryginalna - z zastrzeżeniem, że konwersja spoza zakresu unsigned to signed jest zdefiniowana w implementacji i może wywołać sygnał na przepełnienie.

Aby uzyskać pełną przenośność (co prawdopodobnie będzie przesadą), musisz użyć type punning zamiast conversion. Można to zrobić na dwa sposoby:

Przez wskaźnik rzutów, czyli

uint32_t u = *(uint32_t*)&x;

Na które należy uważać, ponieważ może to naruszać skuteczne zasady typowania (ale jest dobre dla podpisanych/niepodpisanych wariantów typów całkowitych) lub poprzez związki zawodowe, czyli

uint32_t u = ((union { int32_t i; uint32_t u; }){ .i = x }).u;

, który może być również użyty do np. konwersji z double na uint64_t, czego nie można zrobić za pomocą rzutów wskaźnikowych, jeśli chcesz uniknąć niezdefiniowanego zachowania.

 27
Author: Christoph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-21 09:36:31

Odlewy są używane w języku C, aby oznaczać zarówno "konwersję typu", jak i"disambiguację typu". Jeśli masz coś takiego

(float) 3

Wtedy jest to konwersja typu, A rzeczywiste bity się zmieniają. Jeśli powiesz

(float) 3.0

To rodzaj disambiguation.

Zakładając reprezentację dopełniacza (Zobacz komentarze poniżej), kiedy rzucisz int na unsigned int, wzór bitowy nie zostanie zmieniony, tylko jego znaczenie semantyczne; jeśli oddasz go z powrotem, wynik będzie zawsze poprawny. Wpada do przypadek disambiguation typu, ponieważ żadne bity nie są zmieniane, tylko sposób, w jaki komputer je interpretuje.

Zauważ, że teoretycznie dopełniacz 2 nie może być użyty, a unsigned i signed mogą mieć bardzo różne reprezentacje, a rzeczywisty wzór bitowy może się w tym przypadku zmienić.

Jednak od C11 (aktualnego standardu C) masz gwarancję, że sizeof(int) == sizeof(unsigned int):

(§6.2.5/6) dla każdego z podpisanych typów całkowitych istnieje odpowiedni (ale inny) niepodpisany Typ integer (oznaczany z słowo kluczowe unsigned), które wykorzystuje taką samą ilość miejsca (w tym informacja o podpisie) i ma takie same wymagania [...]

Powiedziałbym, że w praktyce można założyć, że jest to bezpieczne.
 6
Author: Filipe Gonçalves,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-21 09:42:27

Powinno to być zawsze bezpieczne, ponieważ typy intXX_t są gwarantowane w dopełniaczu dwójkowym Jeśli istnieją:

7.20.1.1 typy liczby całkowitej o dokładnej szerokości nazwa typedef intN_t oznacza znakowany Typ liczby całkowitej z szerokością N, bez bitów wypełnienia i dwójką reprezentacja uzupełniająca. Tak więc int8_t oznacza taką podpisaną liczbę całkowitą Typ o szerokości dokładnie 8 bitów.

Teoretycznie konwersja wsteczna z uint32_t na {[2] } jest zdefiniowana, tak jak dla wszystkie konwersje unsigned do signed. Ale nie mogę sobie wyobrazić, że platforma zrobiłaby inaczej niż się spodziewasz.

Jeśli chcesz być naprawdę pewien tego, nadal możesz wykonać tę konwersję ręcznie. Musisz tylko sprawdzić wartość > INT32_MAX, a potem trochę policzyć. Nawet jeśli robisz to systematycznie, porządny kompilator powinien być w stanie to wykryć i zoptymalizować.

 2
Author: Jens Gustedt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-21 10:04:29