将字符缓冲区转换为结构体

Convert char buffer to struct

本文关键字:结构体 转换 缓冲区 字符      更新时间:2023-10-16

我有一个char缓冲区buf包含buf[0] = 10, buf[1] = 3, buf[2] = 3, buf[3] = 0, buf[4] = 58

和结构:

typedef struct
{ 
    char type;
    int version;
    int length;
}Header;

我想把buf转换成Header。现在我使用函数

int getByte( unsigned char* buf)
{
    int number = buf[0]; 
    return number;
}
int getInt(unsigned char* buf)
{
    int number =  (buf[0]<<8)+buf[1];
    return number;
}
main()
{
    Header *head = new Header;
    int location = 0;
    head->type = getByte(&buf[location]);
    location++;     // location = 1
    head->version = getInt(&buf[location]);
    location += 2;  // location = 3
    head->ength = getInt(&buf[location]);
    location += 2;  // location = 5 
}

我正在寻找一个解决方案,如

 Header *head = new Header;
 memcpy(head, buf, sizeof(head));

在这种情况下,Header, head->type中的第一个值是正确的,其余的是垃圾。能否将unsigned char* buf转换为Header ?

唯一完全可移植且安全的方法是:

void convertToHeader(unsigned char const * const buffer, Header *header)
{
    header->type = buffer[0];
    header->version = (buffer[1] <<  8) | buffer[2];
    header->length = (buffer[3] <<  8) | buffer[4];
}

void convertFromHeader(Header const * const header, unsigned char * buffer)
{
    buffer[0] = header->type;
    buffer[1] = (static_cast<unsigned int>(header->version) >>  8) & 0xFF;
    buffer[2] = header->version & 0xFF;
    buffer[3] = (static_cast<unsigned int>(header->length) >>  8) & 0xFF;
    buffer[4] = header->length & 0xFF;
}

例子

参见将字节数组转换为整数的解释

编辑

对前面链接的快速总结:其他可能的解决方案(例如memcpyunion)根据不同系统的端序是不可移植的(您所做的可能是至少两个异构系统之间的通信)=>一些系统byte[0]是int的LSB, byte[1]是MSB,而另一些则相反。

另外,由于对齐,struct Header可以大于5个字节(如果对齐是2个字节,在您的情况下可能是6个字节!)(参见这里的例子)

最后,根据某些平台的对齐限制和混叠规则,编译器可能会生成错误的代码。

你需要你的versionlength有相同的长度作为你的buf数组的2个元素;也就是说,您需要使用在<cstdint>中定义的uint16_t类型,而不是可能更长一些的int类型。而且你还需要让buf成为uint8_t的数组,因为char被允许占用超过1个字节!

你可能还需要把type移到最后;否则编译器几乎肯定会在它之后插入一个填充字节,以便能够将version对齐到2字节的边界(一旦你使它成为uint16_t,因此是2字节);然后你的buf[1]就会停在那里,而不是你想要的地方。顺便说一下,这可能是你现在观察到的:通过char后跟int,这可能是4字节,你有3字节的填充,元素13的数组被插入那里(=永远丢失)。

另一个解决方案是修改您的buf数组更长,并有空填充字节,这样数据将实际上与结构体字段对齐。

值得再次提及的是,正如在注释中指出的那样,sizeof(head)返回系统上指针的大小,而不是Header结构体的大小。你可以直接写sizeof(Header);但是在这种微观管理层面,如果你只写"5",你不会失去任何更多的灵活性,真的。

同样,顺序也会给你带来麻烦。处理器没有义务按照您期望的顺序而不是相反的顺序存储数字的字节;毕竟两者都有内在意义。这意味着盲目地将字节buf[0], buf[1]复制到一个数字中可能会导致(buf[0]<<8)+buf[1],但也会导致(buf[1]<<8)+buf[0],如果数据类型是4字节(int通常是),甚至会导致(buf[1]<<24)+(buf[0]<<16)。即使它现在在您的计算机上工作,至少有一个地方相同的代码会导致垃圾。除非,也就是说,这些字节实际上来自于首先重新解释一个数字。然而,在这种情况下,代码是错误的(不可移植的)现在

…值得吗?

考虑到所有的事情,我强烈建议保持你现在处理它们的方式。也许可以简化一下。

将字节转换为int然后再转换为字节,或者获取字节的地址再次对其解引用,也不需要没有描述性名称和除了返回之外没有其他目的的辅助变量,或者是一个你事先知道值的变量。

只做

int getTwoBytes(unsigned char* buf)
{
    return (buf[0]<<8)+buf[1];
}
main()
{
    Header *head = new Header;
    head->type = buf[0];
    head->version = getTwoBytes(buf + 1);
    head->length = getTwoBytes(buf + 3);
}

更好的方法是创建某种序列化/反序列化例程。

同样,我不仅会使用intchar类型,还会使用更具体的int32_t等,这只是平台独立的方式(好吧,实际上你也可以用pragma pack打包你的数据结构)。

    struct Header
    {
        char16_t type;
        int32_t version;
        int32_t length;
    };
    struct Tools
    {
        std::shared_ptr<Header> deserializeHeader(const std::vector<unsigned char> &loadedBuffer)
        {
            std::shared_ptr<Header> header(new Header);
            memcpy(&(*header), &loadedBuffer[0], sizeof(Header));
            return header;
        }
        std::vector<unsigned char> serializeHeader(const Header &header)
        {
            std::vector<unsigned char> buffer;
            buffer.resize(sizeof(Header));
            memcpy(&buffer[0], &header, sizeof(Header));
            return buffer;
        }
    }
    tools;
    Header header = {'B', 5834, 4665};
    auto v1 = tools.serializeHeader(header);
    auto v2 = tools.deserializeHeader(v1);