Безопасно ли reinterpret_cast целое число для float?
Примечание. Я ошибочно спросил о static_cast
изначально; поэтому в верхнем ответе сначала упоминается static_cast
.
У меня есть несколько двоичных файлов с небольшими значениями значений по умолчанию. Я хочу прочитать их машинно-независимым образом. Мои байтовые подпрограммы (из SDL) работают с целыми типами без знака.
Безопасно ли просто использовать между int и float?
float read_float() {
// Read in 4 bytes.
Uint32 val;
fread( &val, 4, 1, fp );
// Swap the bytes to little-endian if necessary.
val = SDL_SwapLE32(val);
// Return as a float
return reinterpret_cast<float &>( val ); //XXX Is this safe?
}
Я хочу, чтобы это программное обеспечение было максимально переносимым.
Ответы
Ответ 1
Ну, static_cast
является "безопасным", и он определил поведение, но это, вероятно, не то, что вам нужно. Преобразование целочисленного значения в тип float просто попытается представить одно и то же целочисленное значение в целевом типе с плавающей запятой. То есть 5
типа int
превратится в 5.0
типа float
(если предположить, что он точно представлен).
То, что вы, кажется, делаете, - это создание представления объекта float
в части памяти, объявленной как переменная Uint32
. Чтобы получить результирующее значение float
, вам необходимо переинтерпретировать эту память. Это достигается с помощью reinterpret_cast
assert(sizeof(float) == sizeof val);
return reinterpret_cast<float &>( val );
или, если хотите, версию указателя одной и той же вещи
assert(sizeof(float) == sizeof val);
return *reinterpret_cast<float *>( &val );
Несмотря на то, что такой тип punning не гарантированно работает в компиляторе, который следует строгому сглаживанию. Другим подходом было бы сделать это
float f;
assert(sizeof f == sizeof val);
memcpy(&f, &val, sizeof f);
return f;
Или вы можете использовать хорошо известный союзный хак для реализации переинтерпретации памяти. Некоторые компиляторы с семантикой строгого сглаживания резервируют подход, основанный на союзе, как официально поддерживаемый метод type-punning
assert(sizeof(float) == sizeof(Uint32));
union {
Uint32 val;
float f;
} u = { val };
return u.f;
Ответ 2
Короче говоря, это неверно. Вы бросаете целое число в float, и оно будет интерпретироваться компилятором как целое в то время. Представленное выше профсоюзное решение работает.
Другой способ сделать то же самое, что и объединение, - это использовать это:
return *reinterpret_cast<float*>( &val );
В равной степени безопасно/небезопасно, как решение для объединения выше, и я определенно рекомендую утверждать, чтобы убедиться, что float имеет тот же размер, что и int.
Я также предупреждал бы, что существуют форматы с плавающей запятой, которые не совместимы с IEEE-754 или IEEE-854 (эти два стандарта имеют одинаковый формат для чисел с плавающей запятой, я не совсем уверен, что такое разница деталей, честно). Итак, если у вас есть компьютер, который использует другой формат с плавающей запятой, он упадет. Я не уверен, есть ли способ проверить это, кроме того, что, возможно, есть консервированный набор байтов, хранящихся где-то, вместе с ожидаемыми значениями в float, затем преобразуйте значения и посмотрите, подходит ли это "правильно".