Законно ли переосмыслить трансляцию в пустоту * - PullRequest
8 голосов
/ 17 апреля 2019

Я смотрел на https://en.cppreference.com/w/cpp/language/reinterpret_cast и заметил, что в нем указаны допустимые типы, к которым мы всегда можем привести:

  • byte*
  • char*
  • unsigned char*

Но я не см. void* в списке. Это недосмотр? Мой вариант использования требует reinterpret_cast, потому что я кастую с int** до void*. И я в конечном итоге произнесу из void* обратно в int**.

Ответы [ 3 ]

5 голосов
/ 17 апреля 2019

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

Соответствующий раздел из cppreference для reinterpret_cast:

(Любой тип указателя объекта T1* может быть преобразован в другой тип указателя объекта cv T2*. Это в точности эквивалентно static_cast<cv T2*>(static_cast<cv void*>(expression)) (что означает, что если *Требование выравнивания 1015 * не является более строгим, чем T1, значение указателя не изменяется, и преобразование результирующего указателя обратно в его исходный тип приводит к исходному значению.) В любом случае результирующий указатель может толькобыть безопасно разыменованным, если это разрешено правилами псевдонимов типов)

При приведении к исходному типу AliasedType и DynamicType одинаковы, поэтому они похожи, что является первым перечисленным случаемпо правилам псевдонимов, где допустимо разыменовывать результат reinterpret_cast:

Всякий раз, когда делается попытка прочитать или изменить сохраненное значениеобъект типа DynamicType через glvalue типа AliasedType, поведение не определено, если не выполняется одно из следующих условий:

  • AliasedType и DynamicType похожи.
  • AliasedType является (возможно, cv-квалифицированным) signed или unsigned вариантом DynamicType.
  • AliasedType - std::byte, (начиная с C ++ 17) char или unsigned char: это позволяет проверять представление объекта любого объекта в виде массива байтов.
4 голосов
/ 17 апреля 2019

[expr.reinterpret.cast] / 7 :

Указатель объекта может быть явно преобразован в указатель объекта другого типа.

[basic.compound] / 3 :

Тип указателя на cv void или указатель на тип объекта:называется тип указателя объекта .

Однако вам не нужно использовать reinterpret_cast.Каждый тип указателя объекта с указанным типом cv-unqualified неявно преобразуется в void*, а обратный может быть выполнен с помощью static_cast.

2 голосов
/ 17 апреля 2019

Всегда допустимо преобразовывать указатель на тип в указатель на другой тип , включая void , поэтому, если T является типом, это допустимо C ++:

T* x;
void *y = reinterpret_cast<void *>(x);

В реальном мире оно никогда не используется, потому что void * - это особый случай, и вы получаете то же значение с помощью static_cast:

void *y = static_cast<void *>(x); // equivalent to previous reinterpret_cast

(на самом деле приведенное выше преобразование является неявным и может быть просто записано void *y = x; - спасибо Майклу Кензелу за то, что он заметил)

Чтобы быть более точным, стандарт даже говорит в черновике n4659 для C ++ 17 8.2.10 Reinterpret cast [expr.reinterpret.cast], §7

Когда значение v из тип указателя объекта преобразуется в тип указателя объекта «указатель на cv T», в результате получается static_cast<cv T*>(static_cast<cv void*>(v)).

Когда вы ссылаетесь на то, что byte и char являются единственными допустимыми типами, это просто то, что разыменование разрешено преобразовывать указатель только для этих типов. void сюда не входит, потому что вы никогда не можете разыменовать void *.


Чтобы конкретно ответить на ваш вопрос

.. Я кастую из int ** в пустоту *. И я в конечном итоге приведу из пустоты * обратно к int **.

Стандарт гарантирует, что первым является стандарт (неявное чтение):

Значение типа «указатель на cv T», где T - тип объекта, может быть преобразовано в значение типа «указатель» резюме ". Значение указателя (6.9.2) не изменяется при этом преобразовании.

Так что это всегда законно:

int **i = ...;
void *v = i;

Для обратного литья стандартно сказано (в параграфе static_cast):

Значение типа «указатель на cv1 void» можно преобразовать в значение типа «указатель на cv2 T»,

Так что это тоже законно

int **j = static_cast<int **>(v);

и стандарт гарантирует, что j == i.

...