我接收和发送的所有数据都对齐到 4 个字节。我知道这个消息系统中的事实。大多数问题不涉及所有准备好的对齐数据,其中源数据和目标数据具有完全相同的对齐方式,没有任何警告,而且大多数时候人们只是跳到“使用 std::bitcast”并且only看结构结构/原始到原始双关语,而不是必须解释的任意数据流。
我有一个包含 uint32_ts 数组的 UDP 数据包,它可能会根据消息类型进行不同的重新解释,但将具有相同的对齐方式。
我首先向具有 uint32_t 对齐数据的任意消息类型的套接字发送命令(没问题,因为我可以将 std::bit_cast 发送到与 uint32_ts 大小相同的 std::array),例如:
enum class Bar : std::uint32_t {
A = 0u,
B,
C,
D
}
struct alignas(4) Foo{
Bar
std::uint32_t x;
std::uint32_t y;
}
...
Foo foo_command = {...};
command_socket.send_to(
asio::buffer(std::bit_cast<std::array<std::uint32_t, sizeof(Foo)/4>>(foo_command)),
command_endpoint, 0, err);
...
然后我得到一个关联的消息类型,它包含多个结构,全部对齐到 4 字节边界。
std::array<std::uint32_t, 1024> recv_buffer;
std::size_t bytes_received = command_socket.receive(asio::buffer(recv_buffer));
我可能有的地方:
enum class ResponseType : uint32_t{
Ok,
NotOk
}
struct alignas(4) Baz{
uint32_t u;
uint32_t i;
uint32_t j;
Bar a;
Bar b;
}
并且
recv_buffer[0]
的类型与ResponseType
相同,其余值对应于Baz.
我想我可以用发送时用的 bitcast 做同样的把戏,但问题是似乎没有办法让固定大小的 std::spans 实际编译,而且我很不清楚,其他人解释标准的方式,无论“切片”是否定义为这样的行为。
给定
Baz baz_response
代替做std::memcpy(&recv_buffer[1], &baz_response, sizeof(baz))
或类似的事情是否安全和明确的行为?
您不能将
std::span
与std::bit_cast
一起使用。
std::bit_cast
作用于对象并进行完整的内存复制,而 std::span
只是对连续数据的容器/视图感知引用。
工作副本会将数据重新解释为具有其大小的指针。
有关跨度位转换的类似问题,请参见this。
对于你的情况,我会
std::memcpy
将数据放入目标中:
std::array<std::byte, 1024> recv_buffer; // uint32_t is technically not allowed to alias the struct
std::size_t bytes_received = command_socket.receive(asio::buffer(recv_buffer));
auto read_data = [&, p_iter = recv_buffer.data(), ep_iter = recv_buffer.data() + bytes_received] <class T> () mutable {
auto new_iter = p_iter + sizeof(T);
if(new_iter > ep_iter) std::terminate(); // error_handling
T t; // NOLINT(cppcoreguidelines-init-variables)
std::memcpy(&t, p_iter, sizeof(T));
p_iter = new_iter;
return t; // return value optimisation
};
auto t = read_data<ResponseType>();
switch (t){
case Ok: handleOK(read_data<Bar>()); break;
case NotOk: handleNotOK(read_data<Baz>());break;
};
其他一些方法就是重新解释你的数据,但我实际上不确定这是否仍然是定义的行为: 我认为
std::array<uint32_t, 3>
不允许使用别名 Bar
,即使它们具有相同的对齐方式。但是你可以确保,使用相同的字节表示(需要相同的对齐方式):
auto const& bar = std::launder(reinterpret_cast<Bar const&>(recv_buffer[1]));
执行此操作时,您应该测试所有编译器在所有目标系统上的行为。