Приведение reinterpret_cast — cppreference.com
Производит приведение одного типа к другому, меняя интерпретацию подлежащего набора битов.
Синтаксис
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, включая |
(начиная с C++11) |
5) Любой указатель на объектный тип T1 может быть преобразован к указателю на другой объектный тип cv T2. Такое приведение эквивалентно <>static_cast<cvT2*>(static_cast<cvvoid*>(выражение)) (подразумевается, что, если требование по выравниванию для 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);
|
(начиная с 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 также разрешены |