Приведение reinterpret_cast
Производит приведение одного типа к другому, меняя интерпретацию подлежащего набора битов.
Синтаксис
reinterpret_cast< целевой-тип >( выражение )
|
|||||||||
Возвращает значение типа целевой-тип.
Объяснение
В отличие от static_cast, но под��бно const_cast, выражение reinterpret_cast не компилируется в какие-либо инструкции ЦП (кроме преобразования между целыми числами и указателями или между указателями на непонятных архитектурах, где представление указателя зависит от его типа). В первую очередь это директива времени компиляции, которая указывает компилятору трактовать выражение, как имеющее тип целевой-тип.
При помощи reinterpret_cast могут быть произведены только следующие преобразования (кроме случаев, когда такие преобразования отменили бы ограничения, налагаемые модификаторами const или volatile):
|
4) Любое значение типа std::nullptr_t, включая
nullptr, может быть преобразовано к любому целочисленному типу, как если бы оно было (void*)0, но никакое значение, в том числе и nullptr, не может быть преобразовано к std::nullptr_t: для этой цели следует использовать static_cast. |
(начиная с C++11) |
T1 может быть преобразован к указателю на другой объектный тип cv T2. Такое приведение эквивалентно <>static_cast<cvT2*>(static_cast<cvvoid*>(выражение)) (подразумевается, что, если требование по выравниванию для T2 не строже, чем для T1, то значение указателя не изменится и обратное приведение указателя к исходному типу даст исходное значение). В любом случае, результат может быть безопасно разыменован, только если это разрешено правилами на псевдоним типа (смотрите ниже) T1 может быть преобразовано в ссылку на другой тип T2. Результатом будет *reinterpret_cast<T2*>(p), где p это указатель типа “указатель на T1” на объект, обозначенный выражением. При этом, ни копии исходного объекта, ни временного объекта не создаётся, а также не вызываются конструкторы и операторы преобразования. Доступ к объекту по результирующей ссылке может быть осуществлён безопасно, только если это разрешено правилами на псевдоним типа (смотрите ниже).dlsym), указатель на функцию может быть преобразован в void* или к любому другому указателю на объект и наоборот. Если реализация поддерживает преобразование в обоих направлениях, то преобразование к исходному типу даст исходное значение, иначе результирующий указатель не может быть разыменован или вызван безопасно.nullptr или любое другое значение типа std::nullptr_t не может быть преобразовано в указатель при помощи reinterpret_cast: для этого должно быть использовано неявное преобразование или приведение static_cast.T1 может быть преобразован в указатель на другой объект-элемент другого класса T2. Если выравнивание T2 не строже, чем выравнивание T1, то преобразование в исходный тип даст исходное значение, в противном случае результирующий указатель не может быть безопасно использован.Как и для всех выражений приведения, результатом будет:
- lvalue, если целевой-тип это ссылочный тип lvalue или ссылка rvalue на функциональный тип (начиная с C++11);
|
(начиная с C++11) |
- иначе prvalue.
Ключевые слова
Псевдоним типа
Любое чтение или изменение хранимого значения объектного типа DynamicType через glvalue типа AliasedType является неопределённым поведением, за исключением случаев:
AliasedTypeиDynamicTypeподобны.AliasedType(возможно cv-квалифицированный) является знаковым или безнаковым вариантом типаDynamicType.AliasedTypeявляется std::byte, (начиная с C++17)charилиunsigned char: это позволяет рассматривать объектное представление любого объекта, как массив байт.
Неформально два типа являются подобными, если, отбросив cv-квалификаторы верхнего уровня:
- они одного типа; или
- они оба указатели и указываемые типы подобны; или
- они оба указатели на элемент одного класса и типы указываемых элементов подобны; или
|
(до C++20) |
|
(начиная с C++20) |
Например:
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::*)()не подобны;int (*)(int *)иint (*)(const int *)не подобны;const int (*)(int *)иint (*)(int *)не подобны;int (*)(int * const)иint (*)(int *)подобны (типы совпадают);std::pair<int, int>иstd::pair<const int, 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, то приведение типа указателя к типу объекта или cv |
(начиная с C++11) |
Параграф стандарта, определяющий правило перекрытия объектов в памяти, содержит два дополнительных пункта, частично перешедших из стандарта C:
AliasedTypeявляется агрегатным типом или типом объединения, который содержит один из ранее упомянутых типов в качестве элемента или нестатического элемента (включая, рекурсивно, элементы подагрегатов и нестатические данные-элементы содержащихся объединений).AliasedTypeявляется (возможно cv-квалифицированным) базовый класс с типомDynamicType.
Эти параграфы описывают ситуации, которые не могут возникнуть в C++ и, таким образом, не рассмотрены выше. В C, доступ к объекту-агрегату при копировании и присваивании осуществляется как к единому целому. Однако в C++ эти действия всегда выполняются через вызов функции-элемента, которая имеет дело с индивидуальными подобъектами, а не с целым объектом (или, в случае объединений, копирует представление объекта через unsigned char). Эти параграфы в конечном итоге были удалены с помощью CWG проблема 2051.
Пример
Демонстрирует некоторые варианты применения reinterpret_cast:
#include <cassert>
#include <cstdint>
#include <iostream>
int f() { return 42; }
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";
else
std::cout << "Порядок байт - big-endian\n";
// псевдоним типа через ссылку
reinterpret_cast<unsigned int&>(i) = 42;
std::cout << i << '\n';
[[maybe_unused]] const int &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
|
выполняет полиморфные преобразования с контролируемым результатом |
| явные приведения | допустимые преобразования между типами |
| стандартные преобразования | неявные преобразования из одного типа в другой |