Почему этот 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 *,

Другие вопросы по тегам