#c
#c
Вопрос:
float f = 12.5;
unsigned int _f = *reinterpret_cast<int*>(amp;f);
std::cout << _f << std::endl; // result: 1095237632
Может кто-нибудь объяснить мне, как работает такое приведение? И что представлено _f
?
РЕДАКТИРОВАТЬ Итак, это число, которое я получил 1095237632
после преобразования в двоичный код, является 0b1000001010010000000000000000000
и это двоичное число находится 12.5
в IEEE-754. Я правильно понял?
Комментарии:
1. «что представлено _f?» Ничего. Вы получаете неопределенное поведение. Посмотрите этот термин.
2. Вас также может заинтересовать физическое представление IEEE-754, посмотрите это.
3. приведение не означало преобразования! любые данные имеют представление в памяти. приведение указывает компилятору использовать данное представление как другой тип. float и int не имеют общего представления, и приведение недопустимо
Ответ №1:
Давайте посмотрим на две функции. Один из них регулярно преобразует float в int, а другой из них переинтерпретирует его с помощью reinterpret_cast:
int cast_to_int(float f) {
// Rounds f to an int, rounding towards 0
return (int)f;
}
int reinterpret_cast_to_int(float i) {
// Just copies the bits
return *reinterpret_cast<int*>(amp;i);
}
Так что же происходит на самом деле? Давайте посмотрим на сборку:
cast_to_int(float):
cvttss2si eax, xmm0 // We cast to an int
ret
reinterpret_cast_to_int(float):
movd eax, xmm0 // We directly copy the bits
ret
В первом случае есть инструкция по сборке, которая выполняет преобразование:
cast_to_int(0.7) -> 0
cast_to_int(1.0) -> 1
cast_to_int(1.5) -> 1
cast_to_int(2.1) -> 2
Во втором случае reinterpret_cast
просто напрямую преобразует базовое представление битов. На самом деле это ничего не делает, и функция просто копирует входной регистр в выходной регистр.
Под капотом float
s имеют совсем другое битовое представление, чем int
s, и именно поэтому вы получаете странные цифры.
Ответ №2:
Никто не может объяснить (*), потому что это не работает.
Из cppreference:
В отличие от static_cast, но подобно const_cast, выражение reinterpret_cast не компилируется ни в какие инструкции процессора (за исключением случаев преобразования между целыми числами и указателями или на неясных архитектурах, где представление указателя зависит от его типа). Это чисто директива времени компиляции, которая предписывает компилятору обрабатывать выражение так, как если бы оно имело тип new_type.
С помощью reinterpret_cast можно выполнить только следующие преобразования, за исключением случаев, когда такие преобразования отбрасывают постоянство или изменчивость.
И затем следует список правил, описывающих, какие переинтерпретации приведений разрешены. Приведение типа A
к совершенно не связанному типу B
не входит в их число, и ваш код демонстрирует неопределенное поведение.
(*) Строго говоря, это неверно. Вы обрабатываете float как int, и если вы посмотрите на их представление на вашем оборудовании и если вы проверите выходные данные вашего компилятора, вы сможете понять, почему вы получаете значение, которое вы получаете, хотя неопределенное поведение не определено, и не стоит вводить детали, если вы не хотите писать непереносимый код.
Комментарии:
1. Для сложных типов поведение было бы неопределенным. Для примитивных типов одинакового размера есть причины делать такие вещи, и поведение четко определено.
2. @AntonF. Я не понимаю, что вы имеете в виду, даже если
float
иint
имеют одинаковый размер, переосмысление afloat*
какint*
было бы неправильным (ну, на самом делеfloat*
иint*
имеют одинаковый размер)3. Итак, это число, которое я получил
1095237632
после преобразования в двоичный код, является0b1000001010010000000000000000000
и это двоичное число находится12.5
в IEEE-754. Я правильно понял?4. @irezwi вы можете проверить это, например, здесь
Ответ №3:
Как говорится в комментариях ниже, использование reinterpret_cast небезопасно для несвязанных типов. Так что не используйте его таким образом.
Во-первых, следует избегать присвоения int uint.
Для систем x86 / x64 значение float обычно представляется в виде 4 байтов, например uint32_t, и они имеют в основном одинаковое выравнивание.
Многие компиляторы допускают следующее: uint32_t _f = * reinterpret_cast(amp;f);
Но это приводит к неопределенному поведению по некоторым причинам (спасибо за комментарии): — оптимизация — выравнивание — …
Вместо этого используйте memcpy.
Если выравнивание такое же, и значения хранятся в памяти, следующий эффект описывает, что происходит при использовании reinterpret_cast:
Ячейка памяти с плавающим значением в 4 байта равна amp;f . При переинтерпретации приведения в uint32_t эта память переинтерпретируется как uint32. Разыменованное значение _f содержит те же байты, что и значение с плавающей точкой f, но интерпретируется как uint32. Вы могли бы привести его обратно и получить исходное значение 12.5:
float f = 12.5;
uint32_t _f = *reinterpret_cast<uint32_t*>(amp;f);
float _fnew = *reinterpret_cast<float*>(amp;_f);
std::cout << _fnew << std::endl; // result: 12.5
reinterpret_cast только переосмысливает ячейки памяти (адреса).
Если выравнивание не совпадает или значения хранятся в регистрах, оптимизированы и т. Д., Приведение может привести к неопределенным значениям.
Комментарии:
1. «float представлен в виде 4 байтов в памяти». не обязательно. И поведение вашего кода не определено.
2. «Следует избегать присваивания int» вводит в заблуждение.
float x = 3.5; int y = x;
все в порядке, если это то, что вы хотите сделать3. @user463035818 Если вы хотите переосмыслить значение, а не получить приведенное значение по некоторым причинам, мой ответ кажется мне правильным и объясняет, что было задано («как работает такое приведение»).).
4. @Bathsheba В общем, вы правы. Я предположил, что системы x86 / x64 (поэтому я отредактировал свой ответ). В более общем случае вы могли бы статически утверждать размер float. Тогда поведение четко определено.
5. «Мне кажется правильным». Это явно не разрешено стандартом. Я не уверен, что еще можно сказать?