c – 将阵列的字节写入POD

前端之家收集整理的这篇文章主要介绍了c – 将阵列的字节写入POD前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
假设我有一组无符号字符,表示一堆POD对象(例如从套接字读取或通过mmap读取).它们表示哪些类型,以及在运行时确定的位置,但是我们假定每个已经正确对齐.

将这些字节“转换”到相应的POD类型中的最好方法是什么?

一个解决方案应该符合c标准(假设为> = c 11),或者至少可以保证使用g> = 4.9,clang> = 3.5和MSVC> = 2015U3.编辑:在x86 / x64或32/64位臂上运行的linux,windows.

理想情况下,我想做这样的事情:

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;
    //...
}

要么

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;
    //...
}

两者似乎都在工作,但两者都是AFAIK未定义的行为c 1).
只是为了完整性:我意识到“通常”的解决方案只是将东西复制到一个适当的局部变量中:

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)第一个是UB,因为它打破了严格的混叠,第二个可能是UB(discussed here),因为标准只是说生成的对象没有被初始化并且具有不确定的值(而不是保证底层的内存是不变的).我相信第一个等效的c代码是很好的定义,所以编译器可能允许这与c-headers兼容,但我不确定.

解决方法

最正确的方法是创建所需POD类的(临时)变量,并使用memcpy()将数据从缓冲区复制到该变量中:
switch(buffer[0]) {
    case 0: {
        Pod1 var;
        std::memcpy(&var,&buffer[4],sizeof var);
        process(var);
        break;
    }
    case 1: {
        Pod2 var;
        std::memcpy(&var,&buffer[8 + buffer[1] * 4],sizeof var);
        process(var);
        break;
    }
    //...
}

这样做的主要原因是由于对齐问题:缓冲区中的数据可能无法正确对齐您正在使用的POD类型.复制消除了这个问题.它也允许您继续使用变量,即使网络缓冲区不再可用.

只有当您绝对确保数据正确对齐时,才能使用您提供的第一个解决方案.

(如果您正在从网络中读取数据,则应始终检查数据是否有效,并且不会在缓冲区外部读取,例如使用& buffer [8缓冲区[1] * 4]您应该检查该地址的开头加上Pod2的大小不会超过缓冲区长度,幸运的是您正在使用uint8_t,否则您还必须检查缓冲区[1]是否为负数.)

猜你在找的C&C++相关文章