2013-03-22 4 views
37

Учитывая following code (и тот факт, что VirtualAlloc() returns a void*):Правильный способ литья типов указателей

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

reinterpret_cast почему выбран вместо static_cast?

Раньше я думал, что reinterpret_cast в порядке, например. (например, DWORD_PTR), но для отливки от void* до BYTE* не является static_cast ОК?

Есть ли какие-либо (тонкие?) Различия в данном конкретном случае, или они являются как действительными, так и действительными указателями?

Есть ли стандарт C++ для этого случая, предлагая путь вместо другого?

+7

На первом месте я бы не стал предполагать, что разработчики Microsoft следуют «де-факто хорошей практике». 'static_cast' здесь просто отлично. – 2013-03-22 20:05:35

+0

'static_cast' должно быть предпочтительным, но некоторые люди предпочитают' reinterpret_cast', потому что имя показывает, что вы делаете (вы переинтерпретируете бит-шаблон). –

+2

(Во всяком случае, +1, наслаждайтесь значком 'nice question'!) – 2013-03-22 20:29:10

ответ

27

Либо акцент допустим для указателей на основные типы, поэтому вы правы, что static_cast в порядке.

При преобразовании между двумя типами указателей возможно, что определенный адрес памяти, содержащийся в указателе, должен быть изменен.

Здесь два отливки отличаются. static_cast сделает соответствующую настройку. reinterpret_cast избежит изменения хранимого значения указателя.

По этой причине, это хорошее общее правило static_cast между типами указателей, если вы не знаете что reinterpret_cast желателен.

5

Использование static_cast для указания места на картинке и обратно от void*.

reinterpret_cast, с другой стороны, гарантирует, что если вы отбросите указатель от одного типа к другому и вернитесь к исходному типу, адрес будет сохранен.

Хотя с большинством реализаций, вы увидите те же результаты при использовании любого из них, static_cast должно быть предпочтительнее.

И с C++11 Я помню, что использование reinterpret_cast для void* имеет четко определенное поведение. До этого такое поведение было запрещено.

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

Предлагаемая резолюция (август, 2010):

Изменить пункт 7 5.2.10 [expr.reinterpret.cast] следующим образом:

указатель на объект может быть явно конвертировано в указатель объекта другой тип. Когда prvalue v типа «указатель на T1» равен , преобразованный в тип «указатель на cv T2», результатом является static_cast (static_cast (v)), если оба T1 и T2 являются стандартными макетами типов (3.9 [ basic.types]), а требования к выравниванию T2 не являются более строгими, чем требования к T1, или если любой тип недействителен.

Преобразование prvalue типа «указатель на Т1» к типу «указатель на Т2» (где T1 и T2 типы объектов и где требования к выравниванию Т2 не являются более жесткими, чем T1) и обратно его исходный тип дает исходное значение указателя. Результат любого другого такого преобразования указателя не указан.

Дополнительная информация here.

Jesse Good для связи.

+2

Здесь [ссылка] (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1120) о реинтерпрет-литье, это было фактически запрещено до C++ 11. –

18

Вы должны static_cast. Используйте static_cast в тех случаях, когда вы уничтожаете неявное преобразование.

В этом конкретном случае, однако, нет разницы, потому что вы конвертируете с void*. Но в целом, reinterpret_cast ния между двумя указателями объекта определяется как (§5.2.10/7):

указатель на объект может быть явно преобразован в указатель на объект такого типа, ди и далее различны. Когда prvalue v типа «указатель на T1» преобразуются к типу «указателю на сорт T2», результата static_cast<cv T2*>(static_cast<cv void*>(v)) если оба T1 и T2 являются типами стандартного макета и требование выравнивания T2 которые нет жестче, чем те, от T1, или если один из типов - void. Преобразование указателя типа «указатель на« T1 »в тип« указатель на T2 »(где T1 и T2 - это типы объектов, а требования к выравниванию T2 не более строгие, чем значения T1) и обратно к исходному типу дает оригинал значение указателя. Результат любого другого такого преобразования указателя не определен.

Emphasis mine. С T1 для вас уже void*, литой до void* в reinterpret_cast ничего не делает. Это не верно, в общем, что и 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); 
} 

Выход:

00CBFD5B
00CBFD5B
00CBFD5C

(Обратите внимание, что из-за y на самом деле не точка до derived, используя это неопределенное поведение.)

Здесь reinterpret_cast имеет другое значение, так как оно проходит через void*. Вот почему вы должны использовать static_cast, когда можете, и reinterpret_cast, когда вам нужно.

+0

Хорошая иллюстрация! Что касается 'void *' conversion - у меня есть это ворчащее воспоминание, что * специально * для конверсий в/из 'void *', стандарт позволяет 'reinterpret_cast' изменять значение. И что только циклическое преобразование 'X *' в 'void *' на 'X * 'гарантируется сохранение адреса. Это неправильно? –

+0

@DrewDormann: Я не уверен, что я следую, на какой строке вы ссылаетесь? Для 'reinterpret_cast' все случаи с' void * '(либо как тип источника, либо тип назначения) проходят через предложение, которое я цитировал, поэтому единственное, что имеет значение с этой точки, -' static_cast' w.r.t. '' Недействительным *. Надеюсь, что это прояснится. – GManNickG

Смежные вопросы