2012-12-21 11 views
17

Możemy przyjrzeć się reprezentacji obiektu typu T, konwertując obiekt T*, który wskazuje na ten obiekt na char*. Przynajmniej w praktyce:Kiedy i jak jest dozwolona konwersja do wskaźnika znaków?

int x = 511; 
unsigned char* cp = (unsigned char*)&x; 
std::cout << std::hex << std::setfill('0'); 
for (int i = 0; i < sizeof(int); i++) { 
    std::cout << std::setw(2) << (int)cp[i] << ' '; 
} 

ten wyprowadza reprezentację 511 na moim systemie: ff 01 00 00.

Występuje tu (na pewno) pewne zachowanie związane z implementacją. Która z odlewów pozwala mi przekonwertować int* na unsigned char* i jakie konwersje powodują, że ta obsada ma miejsce? Czy przywołuję niezdefiniowane zachowanie, gdy tylko rzucę? Czy mogę obsłużyć dowolny typ T*? Na czym mogę polegać, kiedy to robię?

+3

Nie sądzę, że to niezdefiniowane zachowanie, przynajmniej jeśli nie modyfikujesz danych.Ale wynik będzie zależał od tego, czy platforma jest mała, czy wielka endian. – Synxis

+2

Należy pamiętać, że jest to bezpieczne tylko dla 'char *'. Wskaźniki rzutowania, aby były odczytywane jako różne typy, powodują problemy z * aliasingiem *. Języki C i C++ gwarantujące kompilatorowi, który wskazuje na różne typy, nigdy nie mogą wskazywać na ten sam obiekt, więc optymalizator może np. Przechowywać wartość w rejestrze lub podnieść ładunek lub napisać z pętli. "char *" jest jedynym wyjątkiem. Należy przypisać 'char *' do aliasu ze wszystkim, z powodu serializacji do iz buforów dyskowych i sieciowych. –

+2

@ZanLynx - Re "' char * 'jest jedynym wyjątkiem": Niezupełnie. Standard umożliwia również konwersję na "unsigned char *". –

Odpowiedz

12

Który z rzutów pozwala mi przekonwertować int* na unsigned char*?

Odlewanie w stylu C w tym przypadku jest takie samo jak reinterpret_cast<unsigned char*>.

Czy mogę rzucić dowolny typ T * w ten sposób?

Tak i nie. Część TAK: Możesz bezpiecznie rzucić dowolny typ wskaźnika na char* lub unsigned char* (z odpowiednimi kwalifikatorami const i/lub volatile). Rezultat jest zdefiniowany przez implementację, ale jest legalny.

Brak części: Standard wyraźnie zezwala na char* i unsigned char* jako typ docelowy. Jednak nie można (na przykład) bezpiecznie rzucić double* na int*. Zrób to i przekroczyłeś granicę od zachowania zdefiniowanego przez implementację do niezdefiniowanego zachowania. Narusza zasadę ścisłego aliasingu.

+1

Aha, więc wygląda na to (z @ GeneBushuyev i @ nobar's odpowiedzi) obsada od 'T *' do dowolnego 'U *' ma nieokreślony wynik (ale będzie grzywna, jeśli odrzuć ponownie) i gdybym był rzutowany na coś innego niż "char *" lub "unsigned char *", a następnie * dostęp * do obiektu za pomocą tego wskaźnika, miałbym niezdefiniowane zachowanie (jak na ścisłe aliasowanie). Idealna odpowiedź miałaby oba te punkty. ;) –

2

Zachowaniem implementacyjnym w twoim przykładzie jest atrybut endianness twojego systemu, w tym przypadku twój procesor jest trochę endianem.
O rzutowaniu typu, kiedy rzucasz int* na char*, wszystko, co robisz, mówi kompilatorowi, aby interpretować, co oznacza cp jako znak, więc odczyta tylko pierwszy bajt i zinterpretuje go jako znak.

5

Twoje mapy oddanych do:

unsigned char* cp = reinterpret_cast<unsigned char*>(&x); 

Podstawowym reprezentacją int jest wdrożenie zdefiniowane, a oglądanie go jako znaków pozwala na zbadanie tego. W twoim przypadku jest to 32-bitowy mały endian.

Nie ma w tym nic szczególnego - ta metoda sprawdzania reprezentacji wewnętrznej jest ważna dla dowolnego typu danych.

C++ 03 5.2.10.7: Wskaźnik do obiektu można jawnie przekonwertować na wskaźnik na obiekt innego typu. Poza tym, że konwersja wartości typu "wskaźnik do T1" na typ "wskaźnik na T2" (gdzie T1 i T2 są typami obiektów i gdzie wymagania wyrównania T2 nie są bardziej rygorystyczne niż w T1) i powrót do jego oryginalnego typu daje oryginalna wartość wskaźnika, wynik takiej konwersji wskaźnika jest nieokreślony.

To sugeruje, że wyniki oddanych w nieokreślonego zachowania. Ale z praktycznego punktu widzenia rzutowanie z dowolnego typu wskaźnika na char* zawsze pozwoli ci zbadać (i zmodyfikować) wewnętrzną reprezentację przywoływanego obiektu.

+0

Ściśle mówiąc, norma nie gwarantuje, że 'char' jest mniejszy niż' int'. – nobar

+2

Odpowiednie standardy dotyczące "ścisłej reguły aliasingu" podano tutaj: http://stackoverflow.com/a/7005988/86967. Synopsis: Jeśli uzyskasz dostęp do obiektu przez "char *" lub "unsigned char *", nie ma problemu. – nobar

+0

Jest to bardzo styczne, ale warto zauważyć, że _strict aliasing rule_ sugeruje, że użycie 'char *' może kolidować z optymalizacją. W tym miejscu można wprowadzić słowo kluczowe "restrict" (niestandardowe) (http://stackoverflow.com/questions/6434549/does-c11-add-the-c99-restrict-specifier-if-not-why-not) 'restrict' użyteczne - choć nie dotyczy to danego pytania, ponieważ _aliasing_ jest dokładnie punktem pytania. – nobar

1

Odsunięcie między wskaźnikami jest zawsze możliwe, ponieważ wszystkie wskaźniki są niczym więcej niż adresami pamięci i jakikolwiek typ w pamięci może zawsze być traktowany jako ciąg bajtów.

Ale - oczywiście - sposób, w jaki tworzona jest sekwencja, zależy od tego, jak rozkładany typ jest reprezentowany w pamięci, a to wykracza poza zakres specyfikacji C++.

To powiedziawszy, chyba że w bardzo patologicznych przypadkach można się spodziewać, że reprezentacja będzie taka sama we wszystkich kodach tworzonych przez ten sam kompilator dla wszystkich komputerów tej samej platformy (lub rodziny) i nie należy oczekiwać tego samego wyniki na różnych platformach.

W ogólnym jedno uniknięcia jest wyrazić relację między rozmiarach typu jak „predefiniowanych”: w próbce można zakładać sizeof(int) == 4*sizeof(char): to niekoniecznie zawsze prawdziwe.

Ale zawsze jest prawdą, że sizeof (T) = n * sizeof (char), stąd cokolwiek T zawsze może być postrzegane jako liczby całkowitej char-ów

+0

Brak mi miejsca, gdzie OP przyjęło, że 'sizeof (int) == 4 * sizeof (char)'. – phonetagger

+0

@phonetagger Emilio mogło odpowiadać na oryginalną wersję mojego pytania, które opierało się na '4 * sizeof (char)'. –

+0

@ftopbit - Fine. Ale skasuj swój komentarz mówiąc "Nie musisz usuwać ..." – phonetagger

0

Jeśli nie masz operatora odlewu, a następnie cast po prostu mówi, aby "zobaczyć" ten obszar pamięci w inny sposób. Nic tak naprawdę nie wyglądałoby, powiedziałbym.

Następnie odczytujesz bajt po bajcie obszaru pamięci; tak długo jak tego nie zmienisz, wszystko jest w porządku. Oczywiście wynik tego, co widzisz, zależy w dużej mierze od platformy: pomyśl o endianness, rozmiarze słowa, dopełnieniu i tak dalej.

0

Wystarczy odwrócić kolejność bajtów wówczas staje

00 00 01 ff 

Który jest 256 (01) + 255 (ff) = 511

To dlatego platfom jest little endian.

3

Odlew w stylu C w tym przypadku jest równoważny reinterpret_cast. Standard opisuje semantykę w 5.2.10. W szczególności, w punkcie 7:

„wskaźnik do obiektu może być bezpośrednio przekształcony na wskaźnik do inny obiekt type.70 Gdy prvalue v typu«wskaźnik T1»jest przekształcany do rodzaju "Wskaźnik do cvT2", wynikiem jest static_cast<cvT2*>(static_cast<cvvoid*>(v)), jeśli zarówno T1 i T2 są standardowymi typami układu (01), jak i , a wymagania wyrównania dla T2 są nie bardziej rygorystyczne niż te dla T1. Przekształcanie wartości typu "wskaźnik na T1 "Do typu" wskaźnik do T2 "(gdzie T1 i T2 są typami obiektów i , gdzie wymagania wyrównania T2 nie są bardziej rygorystyczne niż te z T1) i powrócić do oryginalnego typu daje pierwotną wartość wskaźnika. Wynikiem jakiejkolwiek innej takiej konwersji wskaźnika jest nieokreślona.”

Co to znaczy w przypadku, wymagania wyrównania są spełnione, a wynik jest nieokreślony.

+0

Ah, więc jest dobrze zdefiniowany tylko wtedy, gdy rzutujesz z 'T *' na 'U *' iz powrotem na 'T *'? Wynik rzutu "T *" do 'U *' jest nieokreślony? Aha. –

Powiązane problemy