2013-03-22 30 views
37

Uwzględniając following code (oraz fakt, że VirtualAlloc() returns a void*):Prawidłowe sposobem odlewania typy wskaźnika

BYTE* pbNext = reinterpret_cast<BYTE*>(
    VirtualAlloc(NULL, cbAlloc, MEM_COMMIT, PAGE_READWRITE)); 

reinterpret_cast dlaczego wybrano zamiast static_cast?

Kiedyś sądziłem, że reinterpret_cast jest OK dla np. wskaźniki odlewania do iz typów całkowitych (jak np. DWORD_PTR), ale rzutowanie z void* na BYTE*, nie jest prawidłowe?

Czy są jakieś (subtelne?) Różnice w tym konkretnym przypadku, czy są one tylko oboma prawidłowymi rzutami wskaźnika?

Czy standard C++ preferuje ten przypadek, sugerując sposób zamiast drugiego?

+7

Po pierwsze, nie zakładam, że programiści Microsoftu przestrzegają "dobrych praktyk de facto". "static_cast" jest w porządku tutaj. –

+0

'static_cast' powinno być preferowane, ale niektórzy wolą' reinterpret_cast', ponieważ nazwa pokazuje, co robisz (reinterpretujesz wzór bitowy). –

+2

(W każdym razie, +1, życzę miłej odznaki!) –

Odpowiedz

27

Obsada jest dopuszczalna dla wskaźników dla podstawowych typów, więc masz rację, że static_cast jest w porządku.

Podczas konwersji między dwoma typami wskaźników: możliwe jest, że określony adres pamięci przechowywany w wskaźniku musi się zmienić.

To tam różnią się dwie odlewy. static_cast dokona odpowiedniej regulacji. reinterpret_cast pozwoli uniknąć zmiany zapisanej wartości wskaźnika.

Z tego powodu, że jest to dobra ogólna reguła static_cast pomiędzy typami wskaźnik chyba wiesz że pożądany jest reinterpret_cast.

5

Używanie do rzutowania wskaźnika na iz adresu void* gwarantuje zachowanie adresu.

z drugiej strony gwarantuje, że jeśli rzutujesz wskaźnik z jednego typu na inny i powrócisz do oryginalnego typu, adres zostanie zachowany.

Mimo że w przypadku większości implementacji można uzyskać takie same wyniki przy korzystaniu z jednego z nich, preferowane powinno być ustawienie static_cast.

I z C++11 Pamiętam, że używanie reinterpret_cast dla void* ma dobrze zdefiniowane zachowanie. Wcześniej to zachowanie było zabronione.

It is not permitted to use reinterpret_cast to convert between pointers to object type and pointers to void. 

Proponowana uchwała (sierpień 2010):

Zmiana 5.2.10 [expr.reinterpret.cast] pkt 7 w następujący sposób:

wskaźnik na obiekt może być jawnie konwertowany do obiektowy wskaźnik innego typu. Gdy prvalue v typu "wskaźnik do T1" jest przekonwertowany na typ "wskaźnik do cv T2", wynikiem jest static_cast (static_cast (v)) jeśli oba T1 i T2 są standardowymi układami typów (3.9 [ basic.types]) i wymagania wyrównania T2 nie są bardziej rygorystyczne niż T1, lub jeśli którykolwiek typ jest nieważny.

Konwersja prvalue typu „wskaźnik T1” do typu „wskaźnik do T2” (gdzie T1 i T2 są typy obiektów i gdzie wymagania centrowania T2 ma bardziej rygorystyczne niż T1) i z powrotem do jego oryginalny typ daje oryginalną wartość wskaźnika. Wynik jakiejkolwiek takiej konwersji wskaźnika nie jest określony.

Więcej informacji here.

Dzięki za link Jesse Good.

+2

Oto [link] (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1120) o reinterpretowaniu obsady, było to faktycznie zabronione przed C++ 11. –

18

Powinieneś static_cast. Użyj static_cast w przypadkach cofania niejawnej konwersji.

W tym konkretnym przypadku nie ma żadnej różnicy, ponieważ konwertujesz z void*. Ogólnie jednak reinterpret_cast ing pomiędzy dwoma wskaźnikami obiekt jest zdefiniowany jako (§5.2.10/7)

wskaźnik na obiekt może być bezpośrednio przekształcony w wskaźnika obiektu typu di ff erent. Kiedy prvalue v typu „wskaźnik do T1” jest konwertowany do typu „wskaźnik do cv T2wynik jest static_cast<cv T2*>(static_cast<cv void*>(v)) jeśli zarówno T1 i T2 są rodzaje standardowego układu i wymagań wyrównania z T2 nie są bardziej restrykcyjne niż te, z T1 lub jeśli dowolny typ to void. Przekształcenie wartości typu "wskaźnik na T1" w typ "wskaźnik na T2" (gdzie T1 i T2 są typami obiektów i gdzie wymagania dotyczące wyrównania T2 nie są bardziej rygorystyczne niż w przypadku T1) i z powrotem do oryginalnego typu daje oryginalny wartość wskaźnika. Wynik jakiejkolwiek takiej konwersji wskaźnika jest nieokreślony.

Podkreślam moją. Od T1 dla ciebie jest już void*, obsada do void* w reinterpret_cast nic nie robi. To nie jest prawda w ogóle, co jest, co Drew Dormann is saying:

#include <iostream> 

template <typename T> 
void print_pointer(const volatile T* ptr) 
{ 
    // this is needed by oversight in the standard 
    std::cout << static_cast<void*>(const_cast<T*>(ptr)) << std::endl; 
} 

struct base_a {}; 
struct base_b {}; 
struct derived : base_a, base_b {}; 

int main() 
{ 
    derived d; 

    base_b* b = &d; // implicit cast 

    // undo implicit cast with static_cast 
    derived* x = static_cast<derived*>(b); 

    // reinterpret the value with reinterpret_cast 
    derived* y = reinterpret_cast<derived*>(b); 

    print_pointer(&d); 
    print_pointer(x); 
    print_pointer(y); 
} 

wyjściowa:

00CBFD5B
00CBFD5B
00CBFD5C

(Zauważ, że ponieważ y nie faktycznie punkt do derived, używanie go jest niezdefiniowanym zachowaniem.)

Tutaj reinterpret_cast pojawia się z inną wartością, ponieważ przechodzi przez void*. Dlatego powinieneś używać static_cast kiedy możesz i reinterpret_cast kiedy musisz.

+0

Ładna ilustracja! Jeśli chodzi o konwersję 'void *' - mam to dręczące wspomnienie, że * specjalnie * dla konwersji z/do 'void *', standard pozwala 'reinterpret_cast' zmienić wartość. I że tylko cykliczna konwersja 'X *' na 'void *' na 'X *' gwarantuje zachowanie adresu. Czy to jest nieprawidłowe? –

+0

@DrewDormann: Nie jestem pewien czy podążam, do której linii się odwołujesz? Dla 'reinterpret_cast', wszystkie przypadki z' void * '(zarówno jako typ źródła, jak i typ docelowy) przechodzą przez klauzulę I cytowaną, więc jedyną rzeczą istotną z tego punktu jest' static_cast' w.r.t. 'void *'. Nadzieja, która wyjaśnia. – GManNickG