如何在不违反类型别名规则的情况下解释消息有效负载?

c--*_*c-- 31 c++

我的程序通过网络接收消息.这些消息被一些中间件(即其他人无法更改的代码)反序列化.我的程序接收到如下所示的对象:

struct Message {
    int msg_type;
    std::vector<uint8_t> payload;
};
Run Code Online (Sandbox Code Playgroud)

通过检查,msg_type我可以确定消息有效负载实际上是,例如,uint16_t值数组.我想在没有不必要的副本的情况下读取该数组.

我的第一个想法是这样做:

const uint16_t* a = reinterpret_cast<uint16_t*>(msg.payload.data());
Run Code Online (Sandbox Code Playgroud)

但是阅读a似乎违反了标准.这是第3.10.10条:

如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:

  • 对象的动态类型,
  • 一个cv限定版本的动态类型的对象,
  • 与对象的动态类型类似的类型(如4.4中所定义),
  • 与对象的动态类型对应的有符号或无符号类型的类型,
  • 一种类型,是有符号或无符号类型,对应于对象动态类型的cv限定版本,
  • 聚合或联合类型,包括其元素或非静态数据成员中的上述类型之一(递归地,包括子聚合或包含联合的元素或非静态数据成员),
  • 一个类型,它是对象动态类型的(可能是cv限定的)基类类型,
  • a charunsigned char类型.

在这种情况下,a将是glvalue并且uint16_t*似乎不符合任何列出的标准.

那么如何在uint16_t不调用未定义的行为或执行不必要的副本的情况下将有效负载视为值数组?

M.M*_*M.M 15

如果您要逐个使用这些值,那么您可以memcpy使用uint16_t或写入payload[0] + 0x100 * payload[1]等,以了解您想要的行为.这不会"效率低下".

如果你必须调用只接受一个数组的函数uint16_t,并且你不能改变传递的结构Message,那么你就不走运了.在标准C++中,您必须制作副本.

如果您正在使用gcc或clang,则另一个选项是-fno-strict-aliasing在编译相关代码时进行设置.

  • 有一个提议(尚未接受)允许零拷贝版本工作:[为低级别对象操作隐式创建对象](http://www.open-std.org/jtc1/sc22/wg21 /docs/papers/2017/p0593r1.html) (4认同)
  • 谢谢.你的第二段 - 特别是"在标准C++中你必须制作副本" - 是我想知道的. (2认同)
  • @SJL您链接到旧版本,使用[此格式](http://wg21.link/p0593)获取最新版本.据我所知,`reinterpret_cast`只适用于尚未"印记"任何东西的"新"字符块; 它不能用于通过value传递的字符的`vector`.因此代码必须是`uint16_t*p = std :: launder(payload.data()); for(int i = 0; i <payload.size()/ 2; ++ i)std :: bless(p + i);`,这是很多"无所事事"的样板.我想这个提案会有进一步的迭代 (2认同)

Dan*_*ica 15

如果您想严格遵循不带UB的C++标准,并且不使用非标准编译器扩展,您可以尝试:

uint16_t getMessageAt(const Message& msg, size_t i) {
   uint16_t tmp;
   memcpy(&tmp, msg.payload.data() + 2 * i, 2);
   return tmp;
}
Run Code Online (Sandbox Code Playgroud)

编译器优化应该避免memcpy在生成的机器代码中复制; 请参阅,例如,类型Punning,严格别名和优化.

事实上,复制到返回值,但根据您将使用它做什么,此副本也可以被优化(例如,此值可以加载到寄存器中并仅在那里使用).

  • 在C++ 20中,[`std :: bit_cast`](https://en.cppreference.com/w/cpp/numeric/bit_cast)应该可以胜任. (10认同)
  • @Paula_plus_plus OP想要读取`uint16_t`值,他们提出的方法是通过创建指针并取消引用它来读取值.这个答案通过不同的方法读取`uint16_t`值,即调用此函数来读取每个值. (4认同)