Приведение 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<cv T2*>(static_cast<cv void*>(выражение)) (подразумевается, что, если требование по выравниванию для T2
не строже, чем для T1
, то значение указателя не изменится и обратное приведение указателя к исходному типу даст исходное значение). В любом случае, результат может быть безопасно разыменован, только если это разрешено правилами на псевдоним типа (смотрите ниже) T1
может быть преобразовано в ссылку на другой тип T2
. Результатом будет *reinterpret_cast<T2*>(p), где p это указатель типа “указатель на T1
” на объект, обозначенный выражением. При этом, ни копии исходного объекта, ни временного объекта не создаётся, а также не вызываются конструкторы и операторы преобразования. Доступ к объекту по результирующей ссылке может быть осуществлён безопасно, только если это разрешено правилами на псевдоним типа (смотрите ниже).dlsym
), указатель на функцию может быть преобразован в void* или к любому другому указателю на объект и наоборот. Если реализация поддерживает преобразование в обоих направлениях, то преобразование к исходному типу даст исходное значение, иначе результирующий указатель не может быть разыменован или вызван безопасно.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 или 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 | выполняет полиморфные преобразования с контролируемым результатом |
явные приведения | допустимые преобразования между типами |
стандартные преобразования | неявные преобразования из одного типа в другой |