将大端浮点数据直接复制到向量 <float> 并就地进行字节交换。安全吗?
Copying big endian float data directly into a vector<float> and byte swapping in place. Is it safe?
我希望能够将大端 float
数组直接从未对齐的网络缓冲区复制到 std::vector<float>
并“就地”执行字节交换回主机顺序,而无需涉及一个中间人 std::vector<uint32_t>
。这还安全吗?我担心 big endian 浮点数据可能会意外地被解释为 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);
注意两个“这样安全吗??”以上评论。
Motivation:我正在编写一个支持 typed arrays 的 CBOR 序列化库。 CBOR 允许类型化数组作为大端或小端传输。
编辑:用memcpy
.
替换了字节序交换循环中的非法reinterpret_cast<uint32_t*>
类型双关语
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
进行浮点数from/tostd::array<uint8_t,4>
的转换。这将是 C++20 中的“正确”方式(您不能将 C 数组直接与 bit_cast 一起使用)。
使用这种方法,您不需要调用 ntohl
,只需按正确顺序复制字节 from/to 缓冲区。
根据 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);
}
我希望能够将大端 float
数组直接从未对齐的网络缓冲区复制到 std::vector<float>
并“就地”执行字节交换回主机顺序,而无需涉及一个中间人 std::vector<uint32_t>
。这还安全吗?我担心 big endian 浮点数据可能会意外地被解释为 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);
注意两个“这样安全吗??”以上评论。
Motivation:我正在编写一个支持 typed arrays 的 CBOR 序列化库。 CBOR 允许类型化数组作为大端或小端传输。
编辑:用memcpy
.
reinterpret_cast<uint32_t*>
类型双关语
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
进行浮点数from/tostd::array<uint8_t,4>
的转换。这将是 C++20 中的“正确”方式(您不能将 C 数组直接与 bit_cast 一起使用)。
使用这种方法,您不需要调用 ntohl
,只需按正确顺序复制字节 from/to 缓冲区。
根据 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);
}