Почему этот reinterpret_cast не компилируется? - PullRequest
59 голосов
/ 05 февраля 2010

Я понимаю, что reinterpret_cast опасно, я просто делаю это, чтобы проверить это. У меня есть следующий код:

int x = 0;
double y = reinterpret_cast<double>(x);

Когда я пытаюсь скомпилировать программу, выдается сообщение об ошибке

недопустимое приведение типа 'float' к типу 'double

Что происходит? Я подумал, что reinterpret_cast был изгоем, который вы могли бы использовать для преобразования яблок в подводные лодки, почему этот простой состав не скомпилируется?

Ответы [ 10 ]

41 голосов
/ 05 февраля 2010

Присваивая 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.

40 голосов
/ 05 февраля 2010

В 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 вам здесь не поможет.

11 голосов
/ 05 февраля 2010

reinterpret_cast не является обычным исполнением. Согласно разделу спецификации C ++ 03 5.2.10.1:

Преобразования, которые могут быть выполнены явно с использованием reinterpret_cast, перечислены ниже. Никакое другое преобразование не может быть выполнено явно с использованием reinterpret_cast.

И в списке нет ничего, что описывает преобразование между целочисленными и плавающими типами (или между целочисленными типами, даже если это недопустимо reinterpret_cast<long>(int(3));)

10 голосов
/ 05 февраля 2010

Если вы пытаетесь преобразовать биты int в представление double, вам нужно привести адрес , а не значение. Вы также должны убедиться, что размеры соответствуют:

uint64_t x = 0x4045000000000000;
double y = *reinterpret_cast<double *>(&x);
3 голосов
/ 05 февраля 2010

Переинтерпретация приведения позволяет вам переинтерпретировать блок памяти как другой тип. Это должно быть выполнено для указателей или ссылок :

int x = 1;
float & f = reinterpret_cast<float&>(x);
assert( static_cast<float>(x) != f );   // !!

Другое дело, что на самом деле это довольно опасное приведение, не только из-за странных значений, появляющихся в результате, или из-за того, что приведенное выше утверждение не дает ошибок, но и потому, что если типы имеют разные размеры, и вы интерпретируете Типы источника «до», любая операция с переинтерпретированной ссылкой / указателем получит доступ к 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 );
3 голосов
/ 05 февраля 2010

Компилятор отвергает то, что вы написали как чепуху, потому что 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]
}
1 голос
/ 05 февраля 2010

reinterpret_cast лучше всего использовать для указателей. Таким образом, указатель на один объект можно превратить в «подводную лодку».

С MSDN :

Оператор reinterpret_cast может быть используется для преобразований типа char * в int * или One_class * для Unrelated_class *, которые по своей сути небезопасный.

Результат reinterpret_cast не может безопасно использоваться ни для чего кроме отбрасывания назад к его оригинальный тип. Другое использование, в лучший, непортативный.

0 голосов
/ 25 января 2019

Переосмысленный подход привел меня по странному пути с непоследовательными результатами. В конце концов я обнаружил, что гораздо лучше использовать memcpy!

double source = 0.0;
uint64_t dest;
memcpy(&dest, &source, sizeof(dest));
0 голосов
/ 05 февраля 2010

Это интересно. Может быть, он выполняет неявное преобразование из int в float, прежде чем попытается привести к удвоению. Типы int и float имеют одинаковый размер в байтах (конечно, в зависимости от вашей системы).

0 голосов
/ 05 февраля 2010

Приведение int к двойному не требует приведения. Компилятор выполнит присваивание неявно.

reinterpret_cast используется с указателями и ссылками, например, приведение int * к double *.

...