C++: безопасный способ привести целое число к указателю

Мне нужно преобразовать целочисленный тип, который содержит адрес, в фактический тип указателя. Я мог бы использовать reinterpret_cast следующим образом:

MyClass *mc1 = reinterpret_cast<MyClass*>(the_integer);

Однако это не выполняет никаких проверок во время выполнения, чтобы увидеть, действительно ли рассматриваемый адрес содержит объект MyClass. Я хочу знать, есть ли какая-либо польза от первого преобразования в void* (с использованием reinterpret_cast), а затем с использованием dynamic_cast для результата. Как это:

void *p = reinterpret_cast<void*>(the_integer);
MyClass *mc1 = dynamic_cast<MyClass*>(p);
assert(mc1 != NULL);

Есть ли преимущества в использовании второго метода?


person Agnel Kurian    schedule 02.12.2009    source источник
comment
Второй метод недопустим в C++, тип выражения для dynamic_cast не может быть void*.   -  person    schedule 02.12.2009
comment
Предполагая, что целочисленное значение изначально было указателем на объект. Тогда int потенциально может не иметь возможности удерживать указатель. Вот почему у нас есть void*. Если вы используете указатели для переноса через нетипизированную границу, вы должны преобразовать их в void* и обратно в исходный тип.   -  person Martin York    schedule 02.12.2009
comment
Он не сказал конкретно «int», тип может быть intptr_t.   -  person    schedule 02.12.2009
comment
@Roger: Если это так, это сработает. Но стоит отметить, что разумнее использовать void*, поскольку именно для этого он и предназначен.   -  person Martin York    schedule 02.12.2009
comment
Итак... как мне переместить указатель через нетипизированную границу? В моем случае я получаю сообщение Win32 и мне нужно преобразовать результат вызова GetWindowLong в указатель. Каков самый безопасный способ сделать это?   -  person Agnel Kurian    schedule 02.12.2009


Ответы (8)


Проверка типов для dynamic_cast реализована по-разному в разных реализациях C++; если вам нужен ответ для вашей конкретной реализации, вы должны указать, какую реализацию вы используете. Единственный способ ответить на вопрос в целом - обратиться к стандарту ISO C++.

По моему чтению стандарта, вызов dynamic_cast для указателя void незаконен:

dynamic_cast<T>(v)

«Если T является типом указателя, v должен быть rvalue указателя на полный тип класса»

(из 5.2.7.2 стандарта ISO C++). void не является полным типом класса, поэтому выражение недопустимо.

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

void * foo = dynamic_cast<void *>(some_pointer);

В этом случае dynamic_cast всегда завершается успешно, и результирующее значение является указателем на наиболее производный объект, на который указывает v.

person Tim Martin    schedule 02.12.2009
comment
Это потому, что после void* foo = dynamic_cast<void*>(some_pointer); вы можете найти foo != some_pointer (что означает, что some_pointer указывал на базовый подобъект). - person ; 02.12.2009

Нет, особого преимущества в этом нет. В тот момент, когда вы используете reinterpret_cast, все ставки сняты. Это зависит от вас, чтобы убедиться, что бросок действителен.

person mmx    schedule 02.12.2009

На самом деле никаких серьезных преимуществ. Если void* указывает на что-то, что не является указателем на полиморфный объект, вы немедленно столкнетесь с неопределенностью поведения (обычно нарушением прав доступа).

person sharptooth    schedule 02.12.2009
comment
Кроме тех случаев, когда вы этого не сделаете. Полагаться на неопределенное поведение никогда не бывает разумной идеей. - person ; 02.12.2009

Безопасный способ — вести учет всех живых объектов MyClass. Лучше всего хранить эту запись в std::set<void*>, что означает, что вы можете легко добавлять, удалять и тестировать элементы.

Причина хранения их как void* заключается в том, что вы не рискуете такими неприятностями, как создание невыровненных указателей MyClass* из ваших целых чисел.

person MSalters    schedule 02.12.2009

  • Во-первых, «переосмысление» int в void * — плохая идея. Если sizeof(int) равно 4, а sizeof(void *) равно 8 (система 64x), это неправильно.

  • Более того, dynamic_cast действителен только для случая полиморфных классов.

person Alexey Malistov    schedule 02.12.2009

Вариант 1 — ваш единственный (полу) переносной/допустимый вариант.

Вариант 2: недействителен C++ как dynamic_cast (поскольку void не допускается).

На уровне реализации требуется информация о типе из исходного типа, чтобы добраться до целевого типа. Невозможно (или не может быть) получить информацию о типе исходного кода во время выполнения из void*, так что это тоже недопустимо.

Dynamic_Cast используется для перехода вверх и вниз по иерархии типов, а не из неизвестных типов.

В качестве примечания вам, вероятно, следует использовать void*, а не целое число для хранения нетипизированного указателя. Существует вероятность того, что int не будет достаточно большим для хранения указателя.

person Martin York    schedule 02.12.2009

Самый безопасный способ работы с указателями в C++ — сделать их безопасными для типов. Это означает:

  • Никогда не храните указатели ни в чем, кроме указателя
  • Избегайте пустых указателей
  • Никогда не передавайте указатели другим процессам
  • рассмотрите weak_ptr, если вы планируете использовать указатели над потоками

Причина этого в том, что то, что вы планируете делать, небезопасно, и этого можно избежать, если только вы не взаимодействуете с небезопасным (устаревшим?) кодом. В этом случае рассмотрите ответ MSalters, но имейте в виду, что это все еще проблема.

person stefaanv    schedule 02.12.2009

Если вы точно знаете, что the_integer указывает на известный базовый класс (у которого есть хотя бы один виртуальный член), на самом деле может быть преимущество: знание того, что объект относится к определенному производному классу. Но вам придется сначала reinterpret_cast обратиться к вашему базовому классу, а затем сделать dynamic_cast:

BaseClass* obj = reinterpret_cast<BaseClass*>(the_integer);
MyClass* myObj = dynamic_cast<BaseClass*>(obj);

Использование void* в dynamic_cast бесполезно и просто неправильно. Вы не можете использовать dynamic_cast, чтобы проверить, есть ли допустимый объект в каком-то произвольном месте в памяти.

Вы также должны быть внимательны при сохранении адресов в переменных не указателя. Существуют архитектуры, в которых sizeof(void*) != sizeof(int), например ЛП64.

person Nikolai Ruhe    schedule 02.12.2009