如果没有 c++20 中的 UB 类型双关数据,我如何安全地将任意单词对齐到结构?

问题描述 投票:0回答:1

我接收和发送的所有数据都对齐到 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))
或类似的事情是否安全和明确的行为?

c++ c++20 undefined-behavior type-punning
1个回答
0
投票

您不能将

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]));

执行此操作时,您应该测试所有编译器在所有目标系统上的行为。

© www.soinside.com 2019 - 2024. All rights reserved.