Пространства имён
Варианты
Действия

Приведение reinterpret_cast

Материал из cppreference.com
< cpp‎ | language
 
 
Язык С++
Общие темы
Управление потоком
Операторы условного выполнения
Операторы итерации (циклы)
Операторы перехода
Функции
Объявление функции
Выражение лямбда-функции
Спецификатор inline
Спецификации динамических исключений(до C++17*)
Спецификатор noexcept(C++11)
Исключения
Пространства имён
Типы
Спецификаторы
decltype(C++11)
auto(C++11)
alignas(C++11)
Спецификаторы длительности хранения
Инициализация
Выражения
Альтернативные представления
Литералы
Логические - Целочисленные - С плавающей запятой
Символьные - Строковые - nullptr(C++11)
Определяемые пользователем(C++11)
Утилиты
Атрибуты(C++11)
Types
Объявление typedef
Объявление псевдонима типа(C++11)
Casts
Неявные преобразования - Явные преобразования
static_cast - dynamic_cast
const_cast - reinterpret_cast
Выделение памяти
Классы
Свойства функции класса
Специальные функции-элементы
Шаблоны
Разное
 
Выражения
Общие
Категории значений (lvalue, rvalue, xvalue)
Порядок оценки (точки последовательности)
Константные выражения
Потенциально оцениваемые выражения
Первичные выражения
Лямбда-выражения(C++11)
Литералы
Целочисленные литералы
Литералы с плавающей запятой
Логические литералы
Символьные литералы, включая управляющие последовательности
Строковые литералы
Литерал нулевого указателя(C++11)
Пользовательский литерал(C++11)
Операторы
a=b, a+=b, a-=b, a*=b, a/=b, a%=b, a&=b, a|=b, a^=b, a<<=b, a>>=b
++a, --a, a++, a--
+a, -a, a+b, a-b, a*b, a/b, a%b, ~a, a&b, a|b, a^b, a<<b, a>>b
a||b, a&&b, !a
a==b, a!=b, a<b, a>b, a<=b, a>=b, a<=>b(начиная с C++20)
a[b], *a, &a, a->b, a.b, a->*b, a.*b
a(...), a,b, a?b:c
выражение new
выражение delete
выражение throw
alignof
sizeof
sizeof...(C++11)
typeid
noexcept(C++11)
Выражения свёртки(C++17)
Альтернативные представления операторов
Приоритет и ассоциативность
Перегрузка операторов
Сравнение по умолчанию(C++20)
Преобразования
Неявные преобразования
Обычные арифметические преобразования
const_cast
static_cast
reinterpret_cast
dynamic_cast
Явные преобразования: (T)a, T(a), auto(a), auto{a}(начиная с C++23)
Пользовательское преобразование
 

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

Содержание

[править]Синтаксис

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

Возвращает значение типа целевой-тип.

[править]Объяснение

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

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

1) Выражение целочисленного типа, перечисление, указатель, или указатель на элемент могут быть преобразованы в свой собственный тип. Результирующее значение совпадает со значением выражения.
2) Указатель может быть преобразован к любому целочисленному типу достаточного размера, вмещающего все значения его типа (например к std::uintptr_t).
3) Значение любого целочисленного типа или перечисления может быть преобразовано в указательный тип. Указатель, преобразованный в целое число достаточного размера, после обратного преобразования к типу того же указателя гарантированно будет иметь исходное значение, в противном случае результирующий указатель не может быть безопасно разыменован (возможность преобразований туда и обратно в противоположном направлении не гарантирована; один и тот же указатель может иметь множество представлений в виде целого числа). Не гарантируется, что константа нулевого указателя NULL или целочисленный ноль, дадут значение нулевого указателя целевого типа; для этой цели следует использовать static_cast или неявное преобразование.
4) Любое значение типа std::nullptr_t, включая nullptr, может быть преобразовано к любому целочисленному типу, как если бы оно было (void*)0, но никакое значение, в том числе и nullptr, не может быть преобразовано к std::nullptr_t: для этой цели следует использовать static_cast.
(начиная с C++11)
5) Любой указатель на объектный тип T1 может быть преобразован к указателю на другой объектный тип cv T2. Такое приведение эквивалентно <>static_cast<cv T2*>(static_cast<cv void*>(выражение)) (подразумевается, что, если требование по выравниванию для T2 не строже, чем для T1, то значение указателя не изменится и обратное приведение указателя к исходному типу даст исходное значение). В любом случае, результат может быть безопасно разыменован, только если это разрешено правилами на псевдоним типа (смотрите ниже)
6)lvalue(до C++11)glvalue(начиная с C++11) выражение типа T1 может быть преобразовано в ссылку на другой тип T2. Результатом будет *reinterpret_cast<T2*>(p), где p это указатель типа “указатель на T1” на объект, обозначенный выражением. При этом, ни копии исходного объекта, ни временного объекта не создаётся, а также не вызываются конструкторы и операторы преобразования. Доступ к объекту по результирующей ссылке может быть осуществлён безопасно, только если это разрешено правилами на псевдоним типа (смотрите ниже).
7) Любой указатель на функцию может быть преобразован к указателю на другой функциональный тип. Вызов функции через указатель на другой функциональный тип приводит к неопределённому поведению, но приведение такого указателя обратно к указателю на исходный функциональный тип даст указатель на исходную функцию.
8) В некоторых реализациях (в частности, в любой POSIX-совместимой системе, как того требует dlsym), указатель на функцию может быть преобразован в void* или к любому другому указателю на объект и наоборот. Если реализация поддерживает преобразование в обоих направлениях, то преобразование к исходному типу даст исходное значение, иначе результирующий указатель не может быть разыменован или вызван безопасно.
9) Нулевой указатель любого типа может быть преобразован к любому другому указательному типу, в результате чего будет получено значение нулевого указателя этого типа. Заметьте, что константа nullptr или любое другое значение типа std::nullptr_t не может быть преобразовано в указатель при помощи reinterpret_cast: для этого должно быть использовано неявное преобразование или приведение static_cast.
10) Указатель на функцию-элемент может быть преобразован в указатель на другую функцию-элемент другого типа. Обратное преобразование к исходному типу даст исходное значение, в противном случае результирующий указатель не может быть безопасно использован.
11) Указатель на объект-элемент класса T1 может быть преобразован в указатель на другой объект-элемент другого класса T2. Если выравнивание T2 не строже, чем выравнивание T1, то преобразование в исходный тип даст исходное значение, в противном случае результирующий указатель не может быть безопасно использован.

Как и для всех выражений приведения, результатом будет:

  • lvalue, если целевой-тип это ссылочный тип lvalue или ссылка rvalue на функциональный тип(начиная с C++11);
  • xvalue, если целевой-тип является ссылкой rvalue на тип объекта;
(начиная с C++11)
  • иначе prvalue.

[править] Ключевые слова

reinterpret_cast

[править]Псевдоним типа

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

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

Неформально два типа являются подобными, если, отбросив cv-квалификаторы верхнего уровня:

  • они одного типа; или
  • они оба указатели и указываемые типы подобны; или
  • они оба указатели на элемент одного класса и типы указываемых элементов подобны; или
  • они оба массивы одинакового размера или оба массивы, чей размер не задан, и типы их элементов подобны.
(до C++20)
  • они оба массивы одинакового размера или, по крайней мере, один из них массив с неизвестной границей, и типы элементов массивов похожи.
(начиная с C++20)

Например:

  • constint*volatile* и int**const подобны;
  • constint(*volatile S::*const)[20] и int(*const S::*volatile)[20] подобны;
  • int(*const*)(int*) и int(*volatile*)(int*) подобны;
  • int(S::*)()const и int(S::*)()не подобны;
  • int(*)(int*) и int(*)(constint*)не подобны;
  • constint(*)(int*) и int(*)(int*)не подобны;
  • int(*)(int*const) и int(*)(int*) подобны (типы совпадают);
  • std::pair<int, int> и std::pair<constint, int>не подобны.

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

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

[править]Примечание

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

struct S1 {int a;} s1;struct S2 {int a;private:int b;} s2;// нестандартная компоновкаunion U {int a;double b;} u ={0};int arr[2];   int* p1 =reinterpret_cast<int*>(&s1);// значение p1 является "указателем на s1.a", т.к.// s1.a и s1 - указательно-приводимы друг в друга   int* p2 =reinterpret_cast<int*>(&s2);// значение p2 не изменяется при помощи// reinterpret_cast и является "указателем на s2".    int* p3 =reinterpret_cast<int*>(&u);// значение p3 является "указателем на u.a": u.a// и u - указательно-приводимы друг в друга   double* p4 =reinterpret_cast<double*>(p3);// значение p4 является "указателем на u.b":// u.a и u.b - указательно-приводимы друг// в друга, т.к. оба - указательно-приводимы// друг в друга через u   int* p5 =reinterpret_cast<int*>(&arr);// значение p5 не изменяется при помощи// reinterpret_cast и является "указателем на arr"

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

struct S {int x;};struct T {int x;int f();};struct S1 : S {};// стандартная компоновкаstruct ST : S, T {};// нестандартная компоновка   S s ={};auto p =reinterpret_cast<T*>(&s);// p - "указатель на s"auto i = p->x;// здесь доступ к элементу класса приводит к неопределённому поведению,// поскольку s не является объектом типа T p->x =1;// неопределённое поведение p->f();// неопределённое поведение   S1 s1 ={};auto p1 =reinterpret_cast<S*>(&s1);// p1 является "указателем на подобъект S объекта s1"auto i = p1->x;// OK p1->x =1;// OK   ST st ={};auto p2 =reinterpret_cast<S*>(&st);// p2 является "указателем на st"auto i = p2->x;// неопределённое поведение p2->x =1;// неопределённое поведение

В этих случаях многие компиляторы выдают предупреждение о "перекрытии объектов в памяти", хотя технически такие выражения вступают в конфликт с чем-то иным, чем параграф, известный как "правило перекрытия объектов в памяти".

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

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

double d =0.1;std::int64_t n; static_assert(sizeof n == sizeof d);// n = *reinterpret_cast<std::int64_t*>(&d); // неопределённое поведениеstd::memcpy(&n, &d, sizeof d);// OK n =std::bit_cast<std::int64_t>(d);// OK

Если реализация предоставляет std::intptr_t и/или std::uintptr_t, то приведение типа указателя к типу объекта или cvvoid к этим типам всегда чётко определено. Однако это не гарантируется для указателя на функцию.

(начиная с C++11)

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

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

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

[править]Пример

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

#include <cassert>#include <cstdint>#include <iostream>int f(){return42;}int main(){int i =7;   // в указатель на целое число и обратноstd::uintptr_t v1 =reinterpret_cast<std::uintptr_t>(&i);// static_cast ошибкаstd::cout<<"Значение &i - 0x"<<std::hex<< v1 <<'\n';int* p1 =reinterpret_cast<int*>(v1);assert(p1 ==&i);   // указатель на функцию в указатель на другую функцию и обратноvoid(*fp1)()=reinterpret_cast<void(*)()>(f);// fp1(); неопределенное поведениеint(*fp2)()=reinterpret_cast<int(*)()>(fp1);std::cout<<std::dec<< fp2()<<'\n';// безопасно   // псевдоним типа через указательchar* p2 =reinterpret_cast<char*>(&i);if(p2[0]=='\x7')std::cout<<"Порядок байт - little-endian\n";elsestd::cout<<"Порядок байт - big-endian\n";   // псевдоним типа через ссылкуreinterpret_cast<unsignedint&>(i)=42;std::cout<< i <<'\n';   [[maybe_unused]]constint&const_iref = i;//int &iref = reinterpret_cast<int&>(const_iref); //ошибка компилятора - нельзя избавится от const//Здесь должен быть применён const_cast: int &iref = const_cast<int&>(const_iref);}

Возможный вывод:

Значение &i - 0x7fff352c3580 42 Порядок байт - little-endian 42

[править]Отчёты о дефектах

Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:

Номер Применён Поведение в стандарте Корректное поведение
CWG 195 C++98 преобразование между указателями на функции и указателями на
объекты не разрешено
сделано условно-поддерживаемым
CWG 658 C++98 результат преобразования указателя не был указан (за исключением
преобразования обратно
в исходный тип)
специфицировано для указателей,
типы которых соответствуют
требованиям выравнивания
CWG 799 C++98 было неясно, какое преобразование идентичности может быть
выполнено с помощью
reinterpret_cast
сделано понятным
CWG 1268 C++11 reinterpret_cast может приводить значения lvalue только к
ссылочным типам
значения xvalue также разрешены

[править]Смотрите также

преобразование const_cast добавляет или удаляет const[править]
преобразование static_cast выполняет основные преобразования[править]
приведение dynamic_cast выполняет полиморфные преобразования с контролируемым результатом[править]
явные приведения допустимые преобразования между типами [править]
стандартные преобразования неявные преобразования из одного типа в другой[править]
close