比方说,我有一个无符号字符数组,代表一堆 POD 对象(例如,从套接字或通过 mmap 读取)。它们代表哪些类型以及在什么位置是在运行时确定的,但我们假设每个类型都已经正确对齐。
将这些字节“转换”为相应 POD 类型的最佳方法是什么?
解决方案应该符合 c++ 标准(假设 >= c++11),或者至少保证可以与 g++ >= 4.9、clang++ >= 3.5 和 MSVC >= 2015U3 配合使用。编辑:在linux、windows上,在x86/x64或32/64位arm上运行。
理想情况下我想做这样的事情:
uint8_t buffer[100]; //filled e.g. from network
switch(buffer[0]) {
case 0: process(*reinterpret_cast<Pod1*>(&buffer[4]); break;
case 1: process(*reinterpret_cast<Pod2*>(&buffer[8+buffer[1]*4]); break;
//...
}
or
switch(buffer[0]) {
case 0: {
auto* ptr = new(&buffer[4]) Pod1;
process(*ptr);
}break;
case 1: {
auto* ptr = new(&buffer[8+buffer[1]*4]) Pod2;
process(*ptr);
}break;
//...
}
Both seem to work, but both are AFAIK undefined behavior in c++1).
And just for completeness: I'm aware of the "usual" solution to just copy the stuff into an appropriate local variable:
Pod1 tmp;
std::copy_n(&buffer[4],sizeof(tmp), reinterpret_cast<uint8_t*>(&tmp));
process(tmp);
在某些情况下,它可能没有开销,而在某些情况下,它甚至可能更快,但除了性能之外,我不再可以,例如就地修改数据,说实话:知道我在内存中的适当位置拥有正确的位但我就是无法使用它们,这让我很恼火。
我想出的一个有点疯狂的解决方案是:
template<class T>
T* inplace_cast(uint8_t* data) {
//checks omitted for brevity
T tmp;
std::memmove((uint8_t*)&tmp, data, sizeof(tmp));
auto ptr = new(data) T;
std::memmove(ptr, (uint8_t*)&tmp, sizeof(tmp));
return ptr;
}
g++ 和 clang++ 似乎能够优化掉这些副本,但我认为这给优化器带来了很大的负担,并可能导致其他优化失败,不适用于const uint8_t*
(虽然我不想实际修改它)并且看起来很糟糕(不认为你会得到过去的代码审查)。
1) The first one is UB because it breaks strict aliasing, the second one is probably UB (discussed here https://stackoverflow.com/questions/14659752/placement-new-and-uninitialized-pod-members) because the standard just says that the resulting object is not initialized and has indeterminate value (instead of guaranteeing that the underlying memory is untouched). I believe the first one's equivalent c-code is well defined, so compilers might allow this for compatibility with c-headers, but I'm unsure of this.