Reinterpret cast c что это

от admin

Синтаксис: приведение типов данных в C и C++

Cи-стиль приведения типов данных доступен и языке C++, но считается не самодостаточным по сравнению с приведением типов в C++. Так как Си-стиль приведения типов не так точен, как C++-стиль приведения и не так заметен. Cи-стиль приведения типов данных может быть использован для преобразования любого типа в любой другой тип, при этом неважно насколько это небезопасное преобразование, например, преобразование целого числа в указатель типа int ). Казалось бы, такое преобразование невозможно, однако компилятор с нами не согласен, но выполнит это приведение. И каков получится результат. ему совершенно не важно. Смотрим синтаксис приведения типов данных согласно Си-стилю:

  • type — тип данных к которому преобразуется значение val

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

Рассмотрим следующий пример использования Си-стиля приведения типа данных int к типу double .

Этот пример приводит int к типу double для того, чтобы при делении 13-ти на 7 избежать усечения результата из-за целочисленного деления. Если вы плохо знакомы с приведением типов данных, прочтите статью: Явное и неявное приведение типов данных.

static_cast — унарная операция приведения типов данных в С++

Операция static_cast доступна только в языке C++. static_cast может быть использована для преобразования одного типа в другой, но она не должна быть использована для выполнения недопустимого преобразования, например, преобразование значения в указатель или наоборот. Рекомендуется пользоваться операцией static_cast , нежели Cи-стилем приведения, потому что static_cast ограничивает недопустимое приведение типов и, следовательно — безопаснее.

Операция static_cast , грубо говоря, — это шаблон функции, в которой необходимо явно указать тип данных для преобразования, то есть задать параметр шаблона.

Итак, в треугольных скобочках указывается тип данных, к которому необходимо преобразовать значение value , которое стоит в круглых скобочках. Смотрим все тот же пример, который приводит int к типу double для того, чтобы при делении 13-ти на 7 избежать усечения результата из-за целочисленного деления.

dynamic_cast — унарная операция приведения типов данных в С++

Операция dynamic_cast доступна только в C++ и имеет смысл только, применительно к членам класса иерархии «полиморфных типов». Динамическое приведение типов данных может быть использовано для безопасного приведения указателя (или ссылки) на суперкласс, в указатель (или ссылку) на подкласс в иерархии классов. Если динамическое приведение типов — недопустимо, так как реальный тип объекта, указывает не на тот тип подкласса, приведение типов не выполнится.

Динамическое приведение указателя

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

Синтаксис указателя динамического приведения:

Динамическое приведение ссылки

При приведении ссылочной переменной, не возможно вернуть указатель, в случае неудачи. Поэтому будет вызвано исключение std::bad_cast (из заголовочного файла <typeinfo> ).

Чтобы безопасно пользоваться динамическим приведением, все вызовы dynamic_cast должны быть обрамлены в блок Try/Catch .

const_cast — константное приведение типов данных

Операция const_cast доступна только в C++. Константное приведение используются, чтобы константную переменную преобразовать в неконстантную. При этом, константным становится возвращаемое значение операции const_cast , а не сама переменная.

Следующий пример преобразует константный указатель на символьную строку в неконстантный указатель на эту же строку.

reinterpret_cast — операция приведения типов данных

Операция reinterpret_cast доступна только в C++ и является наименее безопасной формой приведения типов данных в С++, она позволяет интерпретировать значение в другой тип данных. reinterpret_cast не должна быть использована для приведения иерархии классов или преобразования константных переменных.

Рассмотрим пример использования этой операции приведения, например, чтобы преобразовать целое значение в указатель, нужно написать следующее:

Более подробно о приведении типов данных читайте в статье: Явное и неявное преобразование типов данных.

За кулисами C++: статическое, реинтерпретирующее приведения типов и приведение типов в стиле C

Вы когда-нибудь задумывались, почему приведения типов в стиле C и приведения reinterpret_cast считаются злом? Давайте подробно разберемся, что с ними не так.

Введение

C++ знает 5 разных приведений типов (да, приведение в стиле C не является reinterpret_cast ):

  • static_cast : наименее опасное, может понижать указатели.
  • const_cast : удаляет модификатор const . При неправильном использовании это может быть убийственным, поскольку цель может быть действительно константной, и вы получите какие-нибудь ошибки недопустимого доступа.
  • dynamic_cast : безопасное переключение между классами, требует RTTI: а RTTI в C++ – это то, что часто вообще не включено.
  • reinterpret_cast : преобразует всё, что имеет тот же размер, например, int , в FancyClass* на x86. Теперь это уже не просто приведение, а просто способ сказать компилятору, выбросить информацию о типе и обрабатывать данные по-другому.
  • Приведение в стиле C с использованием синтаксиса (тип)переменная . Худшее из когда-либо изобретенных приведений. Оно пытается выполнить следующие приведения в указанном порядке (также смотрите стандарт C++, 5.4 expr.cast, параграф 5):
    1. const_cast
    2. static_cast
    3. static_cast , за которым следует const_cast
    4. reinterpret_cast
    5. reinterpret_cast , за которым следует const_cast

    А вы думали, что это просто единичное плохое приведение, на самом деле это гидра!

Практическое правило должно быть следующим: никогда не используйте reinterpret_cast или приведение в стиле C; если вам нужно выполнить приведение указателей, выполняйте их приведение через void* и только в случае крайней необходимости используйте reinterpret_cast – что означает, что вам действительно нужно реинтерпретировать данные. Помните, C++ – это экспертный язык, он дает вам полный контроль над вашей машиной, но с силой приходит ответственность!

Пример

Давайте посмотрим, почему «жесткие» приведения на самом деле зло, и почему их следует избегать, как чумы. В этом примере мы предположим следующую иерархию классов:

Теперь рассмотрим следующий код:

Что происходит? Что ж, 1 будет с треском провалиться, 2 будет работать, и 3 зависит от того, правильно ли вы включили заголовок или просто определили типы! Давайте сначала посмотрим на (1): он не работает, потому что указатель ParentWitoutVTable* указывает не на начало вновь созданного объекта Derived в памяти, а на другое место. Например, Visual C++ в качестве первого элемента класса помещает указатель таблицы виртуальных методов, поэтому реальный макет Derived выглядит примерно так:

Таким образом, если мы получим указатель на объект без vtable, он будет указывать на empty , в противном случае доступ через указатель не удастся. Теперь reinterpret_cast не знает об этом, и если вы вызовете функцию Derived , она будет думать, что empty – это vtable. static_cast работает правильно, но для этого он должен знать полное объявление обоих типов. Именно здесь приведение в стиле C опасно: пока доступно полное объявление, всё будет работать нормально без каких-либо предупреждений, но если вы решите использовать предварительное объявление типов, приведение в стиле C всё равно не будет выдавать предупреждение (в то время как static_cast не сработает!) и выполнит reinterpret_cast , который сломает ваш код. Так что имейте это в виду и избегайте приведения в стиле C любой ценой.

Изменение порядка, в котором Derived наследуется от своих родителей, не решает эту проблему. Стандарт C++ не дает никаких гарантий, как будут располагаться члены:

порядок наследования не имеет значения, за исключением случаев, указанных в семантике инициализации конструктором (12.6.2), очисткой (12.4) и разметкой хранилища (9.2, 11.1).

и в упомянутом абзаце говорится:

Требования выравнивания реализации могут привести к тому, что два соседних элемента не будут размещены сразу после друг друга; так же возможны требования к пространству для управления виртуальными функциями (10.3) и виртуальными базовыми классами (10.1).

По сути, компилятор может разместить таблицу виртуальных функций там, где он хочет – например, Visual C++ всегда первой размещает таблицу виртуальных функций, независимо от того, в каком порядке класс наследуется от родителей.

В чём смысл существования reinterpret_cast?

В C++ существует оператор reinterpret_cast , смысл которого заключается в приведении между типами, несовместимыми друг с другом.

Однако подобные преобразования нарушают strict aliasing rule, что провоцирует неопределённое поведение. Те же преобразования, которые этого правила не нарушают, укладываются в const_cast , static_cast и dynamic_cast .

В чём же тогда заключается смысл существования данного оператора, если его использование нарушает стандарт?

reinterpret_cast используется не только для преобразования указателей одного типа в другой. Существует несколько разных преобразований. cppreference.com выделяет 11 вариантов преобразований:

  1. В свой собственный тип
  2. Указателя в интегральный тип
  3. Интегрального типа в указатель
  4. Типа std::nullptr_t в интегральный тип
  5. Указателя одного типа в указатель другого типа
  6. lvalue одного типа в ссылку на другой тип
  7. Указателя на функцию одного типа в указатель на функцию другого типа
  8. Указателя на функцию в void*
  9. Нулевого указателя любого типа в указатель любого другого типа
  10. rvalue указатель одного типа на функцию-член в указатель другого типа на функцию-член
  11. rvalue указатель члена-данных одного типа в указатель ну другой член-данных другого типа

Type aliasing-правила затрагивают только пункты 5 и 6 и результат может быть безопасно использован (т.е. без нарушения strict-aliasing) в следующих случаях:

  • Результирующий тип есть динамический тип исходного объекта
  • Результирующий тип и динамический тип указывают на одинаковый тип T
  • Результирующий тип есть знаковый или беззнаковый вариант типа исходного объекта
  • Результирующий тип есть агрегатный тип или union , в котором содержится элемент или нестатический член данных, используемый в качестве исходного объекта. Т.е. можно получить указатель на структуру по указателю на её член.
  • Результирующий тип есть базовый класс динамического типа исходного объекта и этот тип является standard-layout классом и не содержит нестатических членов-данных, и результирующий тип — первый базовый класс.
  • Результирующий тип есть указатель на char , unsigned char или std::byte .

Некоторые реализации ослабляют эти правила в качестве нестандартных расширений языка.

reinterpret_cast conversion

Преобразование между типами путем переинтерпретации битового паттерна,лежащего в основе.

Syntax

reinterpret_cast < новый тип > ( выражение )

Возвращает значение типа new-type.

Explanation

В отличие от static_cast , но подобно const_cast , выражение reinterpret_cast не компилируется ни в какие инструкции ЦП (за исключением преобразования между целыми числами и указателями или в малоизвестных архитектурах, где представление указателя зависит от его типа). Это чисто директива времени компиляции, которая указывает компилятору обрабатывать выражение, как если бы оно имело тип new-type.

Только следующие преобразования могут быть выполнены с reinterpret_cast , за исключением случаев, когда такие преобразования будут отброшены constness or volatility .

4) Любое значение типа std::nullptr_t , включая nullptr , может быть преобразовано в любой целочисленный тип, как если бы оно было (void*)0 , но никакое значение, даже nullptr , не может быть преобразовано в std::nullptr_t static_cast следует использовать static_cast для этой цели. (since C++11)

Как и в случае со всеми выражениями гипса,результат:

  • l-значение,если new-type является ссылочным типом;
  • r-значение в противном случае.
  • lvalue,если new-type является lvalue ссылочным типом или rvalue ссылкой на тип функции;
  • x-значение,если new-type является r-значением,ссылающимся на тип объекта;
  • в противном случае.

Keywords

Type aliasing

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

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

В неофициальном порядке,два типа similar если,проигнорировав высокоуровневую квалификацию на СВ:

  • они одного типа;или
  • они оба являются указателями,а указательные типы похожи;или
  • они оба являются указателями на членов одного и того же класса,и типы указавших на них членов похожи;или
  • они оба массивы одного размера или оба массива неизвестной границы,а типы элементов массива похожи.
  • они оба являются массивами одинакового размера или,по крайней мере,один из них является массивом неизвестного размера,и типы элементов массива схожи.
  • const int * volatile * и int * * const похожи;
  • const int (* volatile S::* const)[20] и int (* const S::* volatile)[20] похожи;
  • int (* const *)(int *) и int (* volatile *)(int *) похожи;
  • int (S::*)() const и int (S::*)() являются not similar;
  • int (*)(int *) и int (*)(const int *) являются not similar;
  • const int (*)(int *) и int (*)(int *) являются not similar;
  • int (*)(int * const) и int (*)(int *) похожи (они одного типа);
  • std::pair<int, int> и std::pair<const int, int> > not similar.

Данное правило позволяет проводить типовой анализ псевдонимов,при котором компилятор предполагает,что значение,прочитанное через клеймо одного типа,не изменяется при записи на клеймо другого типа (с учетом исключений,отмеченных выше).

Обратите внимание, что многие компиляторы C ++ ослабляют это правило как нестандартное расширение языка, чтобы разрешить доступ неправильного типа через неактивный член объединения (такой доступ не является неопределенным в C).

Notes

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

Выполнение доступа к элементу класса, который назначает нестатический элемент данных или нестатическую функцию-член для glvalue, который фактически не обозначает объект соответствующего типа — например, тот, который получен посредством reinterpret_cast — приводит к неопределенному поведению:

Многие компиляторы выдают предупреждения «строгого наложения» в таких случаях,даже если технически такие конструкции сталкиваются с чем-то отличным от абзаца,известного как «правило строгого наложения».

Цель строгих псевдонимов и связанных с ними правил состоит в том, чтобы включить анализ псевдонимов на основе типов, который был бы уничтожен, если бы программа могла правильно создать ситуацию, в которой два указателя на несвязанные типы (например, int* и float* ) могли бы одновременно существовать и оба могут использоваться для загрузки или хранения одной и той же памяти (см. это письмо на отражателе SG12 ). Таким образом, любая техника, способная создать такую ​​ситуацию, обязательно вызывает неопределенное поведение.

Когда необходимо интерпретировать байты объекта как значение другого типа, можно использовать std::memcpy или std::bit_cast (начиная с C ++ 20):

Если реализация предоставляет std::intptr_t и/или std::uintptr_t , то приведение от указателя к типу объекта или cv void для этих типов всегда хорошо определен. Однако это не гарантируется для указателя функции.

Параграф,определяющий правило строгого алиасинга в стандарте,раньше содержал два дополнительных буллита,частично унаследованных от C:

  • AliasedType является агрегатным типом или типом объединения, который содержит один из вышеупомянутых типов в качестве элемента или нестатического члена (включая, рекурсивно, элементы субагрегатов и элементы нестатических данных содержащихся объединений).
  • AliasedType является (возможно , резюме -qualified) базовый класс из DynamicType .

Эти пункты описывают ситуации, которые не могут возникнуть в C++, и поэтому не рассматриваются в приведенном выше обсуждении. В C агрегатное копирование и присваивание обращаются к агрегатному объекту как целому. Но в C++ такие действия всегда выполняются через вызов функции-члена, которая обращается к отдельным подобъектам, а не ко всему объекту (или, в случае объединений, копирует представление объекта, т. е. через unsigned char ). Эти пули в конечном итоге были удалены через CWG2051 .

Example

Демонстрирует некоторые варианты использования reinterpret_cast :

Defect reports

Следующие отчеты о дефектах,изменяющих поведение,были применены ретроактивно к ранее опубликованным стандартам C++.

Читать:
Чем определяется разрядность регистров

Похожие статьи