将大端浮点数据直接复制到 vector<float> 和字节交换就地安全吗?

问题描述

我希望能够将大端 float 数组直接从未对齐的网络缓冲区复制到 std::vector<float> 并“就地”执行字节交换回主机顺序,而不涉及中间 std::vector<uint32_t>。这甚至安全吗?我担心大端浮点数据可能会意外地被解释为 NaN 并触发意外行为。这是一个合理的担忧吗?

对于这个问题,假设接收数据的主机是小端的。

以下是一些代码,用于演示我正在尝试执行的操作:

std::vector<float> source{1.0f,2.0f,3.0f,4.0f};
std::size_t number_count = source.size();

// Simulate big-endian float values being received from network and stored
// in byte buffer. A temporary uint32_t vector is used to transform the
// source data to network byte order (big endian) before being copied
// to a byte buffer.
std::vector<uint32_t> temp(number_count,0);
std::size_t byte_length = number_count * sizeof(float);
std::memcpy(temp.data(),source.data(),byte_length);
for (uint32_t& datum: temp)
    datum = ::htonl(datum);
std::vector<uint8_t> buffer(byte_length,0);
std::memcpy(buffer.data(),temp.data(),byte_length);
// buffer Now contains the big endian float data,and is not aligned at word boundaries

// copy the received network buffer data directly into the destination float vector
std::vector<float> numbers(number_count,0.0f);
std::memcpy(numbers.data(),buffer.data(),byte_length); // IS THIS SAFE??

// Perform the byte swap back to host order (little endian) in place,// to avoid needing to allocate an intermediate uint32_t vector.
auto ptr = reinterpret_cast<uint8_t*>(numbers.data());
for (size_t i=0; i<number_count; ++i)
{
    // IS THIS SAFE??
    uint32_t datum;
    std::memcpy(&datum,ptr,sizeof(datum));
    *datum = ::ntohl(*datum);
    std::memcpy(ptr,&datum,sizeof(datum));
    ptr += sizeof(datum);
}

assert(numbers == source);

注意两个“这安全吗??”以上评论

动机:我正在编写一个支持 typed arrays 的 CBOR 序列化库。 CBOR 允许类型化数组以大端或小端传输。

EDIT:用 reinterpret_cast<uint32_t*> 替换了端序交换循环中非法的 memcpy 类型双关语。

解决方法

ntohl() 可能会将数据解释为整数(Network TO Host Long)。但可以肯定的是,我建议首先仅使用整数运算进行字节交换,然后将缓冲区处理为浮点向量。

,

编辑后:

关于 auto datum = reinterpret_cast<uint32_t*>(numbers.data());:这在 C++ 中是不允许的,只能安全地将类型双关到 uint8_t(仅当 CHAR_BIT == 8 时,更准确地说,此类型双关异常仅适用于char 种类型)

旧答案: 以下是编辑前的问题(带 bit_cast 的问题)。

这是安全的,前提是 sizeof(float) == sizeof(uint32_t)

不要担心信号 NaN。异常通常被禁用,即使它们被启用,它们也只会在生成信号 NaN 时发生。移动指令不会产生异常。

支持通过 data() 指针访问向量元素(读取和写入)。 vector 保证有连续的存储空间。

但是你为什么不在没有临时缓冲区的情况下只在一个循环中完成所有工作?

只要有浮点向量(输入或输出)和数据缓冲区(uint8_t 向量)。 为了发送只是迭代浮点输入向量,对于每个元素执行字节交换并将 4 个字节写入数据缓冲区。一次一个。那么您不需要任何中间缓冲区。它可能不会更慢。接收则相反。

使用 std::bit_cast 将浮点数从/到 std::array<uint8_t,4> 进行转换。这将是 C++20 中的“正确”方式(您不能通过 bit_cast 直接使用 C 数组)。 使用这种方法,您不需要调用 ntohl,只需按正确顺序从/向缓冲区复制字节即可。

,

根据 Andreas 对单个循环的建议,复制和交换代码看起来像这样(未经测试):

std::vector<float> numbers(number_count,0.0f); // Destination
auto ptr = buffer.data();
for (auto& number: numbers)
{
    uint32_t datum;
    std::memcpy(&datum,ptr,sizeof(datum));
    number = std::bit_cast<float>(endian_swap(datum));
    ptr += sizeof(datum);
}