在C/C++中将uint32_t按位转换为float

11
我从网络接收到一个缓冲区,该缓冲区已转换为32位字的数组。我的接口文档将其中一个字定义为IEEE-754浮点数。我需要从缓冲区中提取这个字。但是,在不进行转换的情况下从一种类型强制转换为另一种类型很困难。这些位已经符合IEEE-754浮点数标准,我不想重新排列任何位。我的第一次尝试是将uint32_t的地址强制转换为void*,然后将void*转换为float*,最后作为float解除引用:
float ieee_float(uint32_t f)
{
    return *((float*)((void*)(&f)));
}

错误: 解引用类型转换指针将违反严格别名规则 [-Werror=strict-aliasing]

我第二次尝试的代码如下:

float ieee_float(uint32_t f)
{
    union int_float{
        uint32_t i;
        float f;
    } tofloat;

    tofloat.i = f;
    return tofloat.f;
}

然而,街上流传着工会完全不安全的说法。从最近没有写入的联合成员中读取是未定义行为。

因此,我尝试了更多的C++方法:

float ieee_float(uint32_t f)
{
  return *reinterpret_cast<float*>(&f);
}

错误:对强制类型转换的指针取消引用将违反严格别名规则[-Werror = strict-aliasing]

我的下一个想法是,“算了吧。我为什么要处理指针呢?”然后尝试:

float ieee_float(uint32_t f)
{
  return reinterpret_cast<float>(f);
}

错误:从类型“uint32_t {aka unsigned int}”到类型“float”的转换无效

是否有一种方法可以在不触发警告/错误的情况下进行转换?我正在使用-Wall -Werror 编译g ++。我希望不要触及编译器设置。

我打上了C标签,因为接受C解决方案。


1
如果你正在编译为C++,那么一个C的解决方案可能不起作用。毕竟不同的语言嘛。或者你打算用C编译器编译那个函数然后链接进来吗? - StoryTeller - Unslander Monica
如果一个C开发者有一个C解决方案,我很乐意接受,因为它很可能也适用于C++。这更多是语法问题而不是其他任何问题。 - Stewart
没有不违反规则或编写不安全代码的方法来完成这个任务。在我看来,联合方法似乎是最好的选择。 - john
2
垃圾。通过联合进行双关语是C的解决方案。这不是未定义行为,并且只要您不最终陷入陷阱表示,它将完全按照您的意愿执行。然而,在C++中,这是未定义的行为。看到双重标记的问题了吗? - StoryTeller - Unslander Monica
sizeof(float) != sizeof(std::uint32_t) 时应该发生什么? - Zereges
float ieee_float(uint32_t f) { void *p = &f; float fv = *(float*)p; return fv; } but the union is cleaner typedef union { float f; uint32_t v; } fu; then float ieee_float(uint32_t f) { fu.v = f; return fu.f; } - David C. Rankin
3个回答

13

在C++20中,您可以使用std::bit_cast

float ieee_float(uint32_t f)
{
    return std::bit_cast<float>(f);
}

在 C++17 及之前,正确的方式™ 是:

float ieee_float(uint32_t f)
{
    static_assert(sizeof(float) == sizeof f, "`float` has a weird size.");
    float ret;
    std::memcpy(&ret, &f, sizeof(float));
    return ret;
}

无论是GCC还是Clang在-O1及以上级别为此代码生成相同的汇编,对于一个天真的reinterpret_cast<float &>(f)也是如此(但后者是未定义行为,在某些情况下可能无法工作)。


1
@Zereges - 所有版本最终都会进行复制。毕竟它是按值返回的。 - StoryTeller - Unslander Monica
1
我会使用 sizeof(uint32_t),而不是使用 4 - user694733
2
这里有一件武器,希望能帮到你:[basic.val]/11.8。记得不要独自前行! - YSC
1
@user694733 或 sizeof f - Zereges
1
@Aminos 这整个页面都是关于 reinterpret_cast 的,而第6点则是关于将其转换为 T2类型的引用,这意味着使用类型为 T1lvalue 进行 reinterpret_cast<T2&>(lvalue) - Sebastian
显示剩余6条评论

2
没有C/C++语言。它们是具有不同规则的不同语言。在C中,有效的方法是使用联合,但在C++中不允许这样做。请参见: 在早期的C++标准中,您必须使用std::memcpy。即使用于类型转换的reinterpret_cast也会引发未定义的行为,因此被禁止。在C++20中,创建了一种新的转换类型std::bit_cast,专门用于此目的。
float ieee_float(uint32_t f)
{
  return std::bit_cast<float>(f);
}

参见:

根据类型转换规则,某些强制类型转换是允许的。 - Sebastian
@Sebastian,你在说哪种类型转换?除了 bit_cast 之外,没有其他允许进行类型切换的方法。 - phuclv
请参见类型别名部分:https://en.cppreference.com/w/cpp/language/reinterpret_cast 允许转换为 std::bytecharunsigned char(但如果与平台相关,则不允许转换为 signed char)。 - Sebastian

0

你有几个选择,如此处所述:

  • 使用union解决方案:自C11以来,它已明确允许(如其他答案中所述)。
  • 不要使用32位字数组,而是使用8位字节数组(uint8_t),因为char类型可以别名为任何类型。

问题已经重新标记为纯C++,因此(1)不适用。另外,我非常怀疑(2)是否定义明确... - HolyBlackCat
IRC,任何类型都可以别名为“unsigned char”。 - YSC
1
@YSC 是的,但如果我没记错的话,反过来就不行了。 - HolyBlackCat
@HolyBlackCat 就是这样。 - YSC
在C99中,使用“union”进行类型转换也是允许的。即使在C90中,如果实现支持它,也是允许的。 - user694733

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接