Почему этот reinterpret_cast не компилируется?
Я это понимаю reinterpret_cast
опасно, я просто делаю это, чтобы проверить это. У меня есть следующий код:
int x = 0;
double y = reinterpret_cast<double>(x);
Когда я пытаюсь скомпилировать программу, выдает ошибку
недопустимое приведение типа 'float' к типу 'double
В чем дело? я думал reinterpret_cast
Был ли этот жулик, который вы могли бы использовать для преобразования яблок в подводные лодки, почему этот простой состав не скомпилируется?
10 ответов
Присваивая y значению, возвращенному приведением, вы на самом деле не приводите значение x
конвертируешь. То есть, y
не указывает на x
и притворяться, что это указывает на поплавок. Преобразование создает новое значение типа float
и присваивает ему значение из x
, Есть несколько способов сделать это преобразование в C++, среди них:
int main()
{
int x = 42;
float f = static_cast<float>(x);
float f2 = (float)x;
float f3 = float(x);
float f4 = x;
return 0;
}
Единственная реальная разница, заключающаяся в последнем (неявное преобразование), генерирует диагностику компилятора на более высоких уровнях предупреждений. Но все они функционально делают одно и то же - и во многих случаях фактически то же самое, что и в том же машинном коде.
Теперь, если вы действительно хотите притвориться, что x
это поплавок, то вы действительно хотите бросить x
, делая это:
#include <iostream>
using namespace std;
int main()
{
int x = 42;
float* pf = reinterpret_cast<float*>(&x);
(*pf)++;
cout << *pf;
return 0;
}
Вы можете видеть, насколько это опасно. На самом деле, когда я запускаю это на моей машине, вывод 1
что явно не 42+1.
В C++ reinterpret_cast
может выполнять только определенный набор преобразований, явно указанных в спецификации языка. Короче, reinterpret_cast
может выполнять только преобразования указатель-указатель и преобразования указатель-на-ссылку (плюс преобразование указатель-в-целое и целое-в-указатель). Это согласуется с намерением, выраженным в самом имени приведения: оно предназначено для реинтерпретации указателя / ссылки.
То, что вы пытаетесь сделать, это не реинтерпретация. Если вы хотите переосмыслить int
как double
вам придется преобразовать его в ссылочный тип
double y = reinterpret_cast<double&>(x);
хотя эквивалентная реинтерпретация на основе указателей, вероятно, более явная
double y = *reinterpret_cast<double*>(&x); // same as above
Обратите внимание, что в то время как reinterpret_cast
может преобразовывать типы ссылки / указателя, фактическая попытка чтения данных через результирующую ссылку / указатель приводит к неопределенному поведению.
И в любом случае это, конечно, не имеет особого смысла на платформе с int
а также double
разного размера (так как в случае большего double
вы будете читать вне памяти, занятой x
).
Итак, в конце концов все сводится к тому, чего вы пытались достичь. Реинтерпретация памяти? Смотри выше. Какой-то более значимый int
в double
преобразование? Если так, reinterpret_cast
здесь тебе не поможет
Если вы пытаетесь преобразовать биты вашего int
для представления double
, вам нужно привести адрес, а не значение. Вы также должны убедиться, что размеры соответствуют:
uint64_t x = 0x4045000000000000;
double y = *reinterpret_cast<double *>(&x);
reinterpret_cast не является общим актером. Согласно спецификации раздела C++03 5.2.10.1:
Преобразования, которые могут быть выполнены явно с использованием reinterpret_cast, перечислены ниже. Никакое другое преобразование не может быть выполнено явно с использованием reinterpret_cast.
И нет ничего перечисленного, описывающего преобразование между целочисленными типами и типами с плавающей запятой (или между целочисленными типами, даже если это незаконно reinterpret_cast<long>(int(3));
)
Компилятор отвергает то, что вы написали как чушь, потому что int
а также double
могут быть объекты с разными размерами. Таким способом вы можете добиться того же эффекта, хотя это, безусловно, опасно:
int x = 0;
double y = *reinterpret_cast<double*>(&x);
Это потенциально опасно, потому что если x
а также y
разные размеры (скажем, int
четыре байта и double
восемь байтов), то, когда вы разыменовываете восемь байтов памяти в &x
заполнить y
вы получите четыре байта x
и четыре байта... что будет дальше в памяти (возможно, начало y
или мусор, или что-то еще целиком.)
Если вы хотите преобразовать целое число в двойное, используйте static_cast
и он будет выполнять преобразование.
Если вы хотите получить доступ к битовой структуре x
приведите к удобному типу указателя (скажем, byte*
) и доступ к sizeof(int) / sizeof(byte)
:
byte* p = reinterpret_cast<byte*>(&x);
for (size_t i = 0; i < sizeof(int); i++) {
// do something with p[i]
}
Переинтерпретация приведений позволяет вам переинтерпретировать блок памяти как другой тип. Это должно быть выполнено на указателях или ссылках:
int x = 1;
float & f = reinterpret_cast<float&>(x);
assert( static_cast<float>(x) != f ); // !!
Другое дело, что на самом деле это довольно опасное приведение, не только из-за странных значений, появляющихся в результате, или из-за того, что приведенное выше утверждение не дает ошибок, но и потому, что если типы имеют разные размеры, и вы переосмысливаете "источник" в типы 'destination', любая операция с переинтерпретированной ссылкой / указателем получит доступ sizeof(destination)
байт. Если sizeof(destination)>sizeof(source)
тогда это выйдет за пределы фактической памяти переменных, потенциально убивая ваше приложение или перезаписав другие переменные, отличные от источника или места назначения:
struct test {
int x;
int y;
};
test t = { 10, 20 };
double & d = reinterpret_cast<double&>( t.x );
d = 1.0/3.0;
assert( t.x != 10 ); // most probably at least.
asswet( t.y != 20 );
Реинтерпретативный подход привел меня на странный путь с непоследовательными результатами. В конце концов, я обнаружил, что гораздо лучше использовать memcpy!
double source = 0.0;
uint64_t dest;
memcpy(&dest, &source, sizeof(dest));
Это интересно. Может быть, он выполняет неявное преобразование из int в float, прежде чем попытается привести к удвоению. Типы int и float имеют одинаковый размер в байтах (конечно, в зависимости от вашей системы).
reinterpret_cast
лучше всего использовать для указателей. Таким образом, указатель на один объект можно превратить в "подводную лодку".
Из MSDN:
Оператор reinterpret_cast может использоваться для преобразований, таких как char* в int * или One_class * в Unrelated_class *, которые по своей сути небезопасны.
Результат reinterpret_cast нельзя безопасно использовать для чего-либо, кроме приведения к исходному типу. Другое использование, в лучшем случае, непереносимо.
Используйте союз. Это наименее подверженный ошибкам способ сопоставления памяти между целочисленным типом и типом с плавающей запятой. Переинтерпретация указателя вызовет предупреждения о наложении имен.
#include <stdio.h>
#include <stdint.h>
int main(int argc, char *argv[])
{
union { uint32_t i; float f; } v; // avoid aliasing rules trouble
v.i = 42;
printf("int 42 is float %f\n", v.f);
v.f = 42.0;
printf("float 42 is int 0x%08x\n", v.i);
}
Приведение int к двойному не требует приведения. Компилятор выполнит присваивание неявно.
Reinterpret_cast используется с указателями и ссылками, например, приведение int *
к double *
,