Подтвердить что ты не робот

Безопасно ли 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?
}

Я хочу, чтобы это программное обеспечение было максимально переносимым.

4b9b3361

Ответ 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, затем преобразуйте значения и посмотрите, подходит ли это "правильно".