如何在不违反类型别名规则的情况下解释消息负载?
How do I interpret a message payload without violating type aliasing rules?
我的程序通过网络接收消息。这些消息由某些中间件反序列化(即我无法更改的其他人的代码)。我的程序接收如下所示的对象:
struct Message {
int msg_type;
std::vector<uint8_t> payload;
};
通过检查msg_type
我可以确定消息有效负载实际上是,例如,一个uint16_t
值数组。我想在没有不必要的副本的情况下读取该数组。
我的第一个想法是这样做:
const uint16_t* a = reinterpret_cast<uint16_t*>(msg.payload.data());
但是,从a
中读取似乎违反了标准。以下是条款 3.10.10:
如果程序尝试通过以下类型之一以外的 glvalue 访问对象的存储值,则行为是未定义的:
- 对象的动态类型,
- 对象的动态类型的 CV 合格版本,
- 与对象的动态类型类似(如 4.4 中所定义)的类型,
- 一种类型,该类型是对应于对象的动态类型的有符号或无符号类型,
一种类型,- 该类型是与对象的动态类型的 CV 限定版本相对应的有符号或无符号类型,
在其元素或非静态数据成员中包含上述类型之一的聚合或联合- 类型(递归地包括子聚合或包含的联合的元素或非静态数据成员),
- 一种类型,该类型是对象的动态类型的基类类型(可能符合 CV 标准),
char
或unsigned char
类型。
在这种情况下,a
将是glvalue,uint16_t*
似乎不符合任何列出的标准。
那么,如何在不调用未定义的行为或执行不必要的复制的情况下将有效负载视为uint16_t
值数组呢?
如果你要一个接一个地使用这些值,那么你可以memcpy
到一个uint16_t
,或者写payload[0] + 0x100 * payload[1]
等,关于你想要的行为。这不会是"低效的"。
如果你必须调用一个只接受uint16_t
数组的函数,并且你不能改变提供Message
的结构,那么你就不走运了。在标准C++中,您必须制作副本。
如果您使用的是 gcc 或 clang,另一种选择是在编译相关代码时设置-fno-strict-aliasing
。
如果你想严格遵循没有UB C++标准,并且不使用非标准的编译器扩展,你可以尝试:
uint16_t getMessageAt(const Message& msg, size_t i) {
uint16_t tmp;
memcpy(&tmp, msg.payload.data() + 2 * i, 2);
return tmp;
}
编译器优化应避免在生成的机器代码中memcpy
复制;例如,请参阅类型双关语、严格别名和优化。
事实上,有复制到返回值中,但根据你将如何处理它,这个拷贝也可以被优化掉(例如,这个值可以加载到寄存器中并只在那里使用)。
如果你想严格正确,正如你引用的标准所说,你不能。 如果您希望明确定义行为,则需要进行复制。
如果代码是可移植的,则需要以任何一种方式处理字节序,并从单个uint8_t字节重建uint16_t值,根据定义,这需要一个副本。
如果你真的知道自己在做什么,你可以忽略标准,只做你描述的reinterpret_cast。
GCC 和 clang 支持-fno-strict-aliasing
以防止优化生成损坏的代码。 据我所知,在撰写本文时,Visual Studio编译器没有标志,并且从不执行此类优化 - 除非您使用declspec(restrict)
或__restrict
。
您的代码可能不是 UB(或边界线,具体取决于读者的敏感性),例如,如果vector
数据是这样构建的:
Message make_array_message(uint16_t* x, size_t n){
Message m;
m.type = types::uint16_t_array;
m.payload.reserve(sizeof(uint16_t)*n);
std::copy(x,x+n,reinterpret_cast<uint16_t*>(m.payload.data()));
return m;
}
在此代码中,向量的数据保存一个uint16_t
序列,即使它被声明为uint8_t
。因此,使用此指针访问数据:
const uint16_t* a = reinterpret_cast<uint16_t*>(msg.payload.data());
完全没问题。但是以uint8_t
访问vector
的数据将是UB。访问a[1]
适用于所有编译器,但在当前标准中它是 UB。这可以说是标准中的一个缺陷,c++ 标准化委员会正在努力修复它,请参阅 P0593 用于低级对象操作的隐式对象创建。
截至目前,在我自己的代码中,我不处理标准中的缺陷,我更喜欢遵循编译器行为,因为对于这个主题,这是制定规则的编码者和编译器,标准只会遵循!
- 在没有太多条件句的情况下,我如何避免被零除
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 在未初始化映射的情况下,将值插入到映射的映射中
- 是默认情况下分配给char数组常量的值
- 为什么我不能在不创建字符串变量的情况下使用函数的字符串输出
- 如何在不产生任何垃圾的情况下获得C中的像素
- 在已经使用Git的情况下减少编译时间
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- 如何在没有信号的情况下从C++执行QML插槽
- 如何在不知道向量大小的情况下输入向量内部的向量?
- 我是c ++的新手,你能解释一下在这种情况下的指针吗
- 如何在不违反类型别名规则的情况下解释消息负载?
- 寻找有关为什么此C++代码在没有引用的情况下不起作用的解释
- 在没有额外成员的情况下重新解释转换为派生类可以吗
- 我可以在不复制的情况下将 std::vector 重新解释<char>为 std::vector<无符号字符>吗?
- 为什么 UMDH 找不到我的符号?我可以在没有它们的情况下解释堆栈跟踪吗?
- 有人能向我解释一下在这种情况下发生了什么吗
- 如何在不引起UB的情况下将字节序列重新解释为POD结构
- 解释了c++中不同情况下的箭头和点运算符
- 我可以在不更改数据的情况下更改mat解释吗?(说镜像)