重新Interalpret_cast浮动整数是安全的吗?

Is it safe to reinterpret_cast an integer to float?

本文关键字:安全 整数 Interalpret cast 重新      更新时间:2023-10-16

注意:我最初错误地询问了有关static_cast的问题;这就是为什么首先提到static_cast的原因。

我有一些二进制文件,却很少。我想以与机器无关的方式阅读它们。我的字节交换例程(来自SDL)在未签名的整数类型上运行。

简单地在ints和floats之间施放安全吗?

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?
}

我希望该软件尽可能便携。

好吧, static_cast是"安全的",它已经定义了行为,但这可能不是您需要的。将积分值转换为浮点类型将只是尝试在目标浮点类型中表示相同的积分值。IE。int类型的5将变成float类型的5.0(假设它是可以准确表示的)。

您似乎正在做的是在将float值的对象表示形式构建为Uint32变量的一段记忆中。要产生所得的float值,您需要 retontret 该内存。这将由reinterpret_cast

实现
assert(sizeof(float) == sizeof val);
return reinterpret_cast<float &>( val );

或,如果您喜欢的,则是同一物品的指针版

assert(sizeof(float) == sizeof val);
return *reinterpret_cast<float *>( &val );

尽管不能保证这种类型的类型能够在严格启用语义的编译器中起作用。另一种方法是这样做

float f;
assert(sizeof f == sizeof val);
memcpy(&f, &val, sizeof f);
return f;

,否则您可能能够使用著名的联合骇客来实现内存重新诠释。在C (未定义的行为)中,这是正式非法的,这意味着该方法只能与某些实现一起使用,以将其作为扩展名

assert(sizeof(float) == sizeof(Uint32));
union {
  Uint32 val; 
  float f;
} u = { val };
return u.f;

简而言之,这是不正确的。您正在将整数投放到浮子上,并将其解释为当时的整数。上面提出的工会解决方案。

另一种做与联盟相同的事情的方法是使用它:

return *reinterpret_cast<float*>( &val );

它与上面的联合解决方案同样安全/不安全,我绝对建议一个断言,以确保浮子的大小与int相同。

我还要警告说,有一些不是IEEE-754或IEEE-854兼容的浮点格式(这两个标准具有相同的浮点数格式,我不完全确定该细节差异是什么诚实的)。因此,如果您有一台使用不同浮点格式的计算机,则它将掉落。我不确定是否有任何方法可以检查一下,除了将罐装字节存储在某个地方,以及浮动中的预期值之外,然后转换值并查看是否出现"正确"。

(正如其他人所说的那样,重新诠释的铸件,基础内存被对待,好像是另一种类型,是不确定的行为/放置在记忆中。)

这是ANT的Memcpy解决方案的模板实现,该解决方案避免了-Wstrict-aliasing警告。

我猜这支持了大小不是标准尺寸的实现,但仍然匹配模板大小之一 - 然后如果没有匹配项,则无法编译。

(使用-fstrict-aliasing -Wall编译实际启用-Wstrict-aliasing

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <type_traits>
template<size_t S> struct sized_uint;
template<> struct sized_uint<sizeof(uint8_t)> { using type = uint8_t; };
template<> struct sized_uint<sizeof(uint16_t)> { using type = uint16_t; };
template<> struct sized_uint<sizeof(uint32_t)> { using type = uint32_t; };
template<> struct sized_uint<sizeof(uint64_t)> { using type = uint64_t; };
template<size_t S> using sized_uint_t = typename sized_uint<S>::type;
template<class T> sized_uint_t<sizeof(T)> bytesAsUint(T x)
{
    sized_uint_t<sizeof(T)> result;
    // template forces size to match. memcpy handles alignment
    memcpy(&result, &x, sizeof(x));
    return result;
}
template<size_t S> struct sized_float;
template<> struct sized_float<sizeof(float)> { using type = float; };
template<> struct sized_float<sizeof(double)> { using type = double; };
template<size_t S> using sized_float_t = typename sized_float<S>::type;
template<class T> sized_float_t<sizeof(T)> bytesAsFloat(T x)
{
    sized_float_t<sizeof(T)> result;
    memcpy(&result, &x, sizeof(x));
    return result;
}
// Alt for just 'float'
//template<class T> std::enable_if_t<sizeof(T) == sizeof(float), float> bytesAsFloat(T x)
//{
//    float result;
//    memcpy(&result, &x, sizeof(x));
//    return result;
//}
float readIntAsFloat(uint32_t i)
{
    // error: no matching function for call to 'bytesAsFloat(uint16_t)'
    //return bytesAsFloat((uint16_t)i);
    return bytesAsFloat(i);
}
void printFloat(float f) {
    // warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
    //printf("Float %f: %x", f, reinterpret_cast<unsigned int&>(f));
    printf("Float %f: %x", f, bytesAsUint(f));
}

(Godbolt)