将字符缓冲区转换为结构

Convert char buffer to struct

我有一个 char 缓冲区 buf 包含 buf[0] = 10buf[1] = 3buf[2] = 3buf[3] = 0buf[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));

在此,Headerhead->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;
}

Example

请参阅 了解说明

编辑

先前link的快速总结:其他可能的解决方案(例如memcpyunion)根据不同系统的字节顺序不可移植(做你做的可能是为了至少两个异构系统之间的一种通信)=> 一些系统 byte[0] 是 int 的 LSB,byte[1] 是 MSB,而其他系统则相反。

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

最后,根据某些平台上的对齐限制和别名规则,编译器可能会生成不正确的代码。

你想要的东西需要你的 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) returns 是您系统中指针的大小,而不是 Header 结构。可以直接写sizeof(Header);但在这种微观管理级别,如果您只写“5”,您将不会失去更多的灵活性,真的。

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

...值得吗?

考虑到所有因素,我的强烈建议是保持您现在处理它们的方式。也许简化一下。

将一个字节转换为一个整数然后再转换为一个字节,或者获取一个字节的地址以再次取消引用它真的没有意义,也不需要没有描述性名称且没有其他用途的辅助变量而不是被返回,或者一个你事先知道其值的变量。

随心所欲

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);
}

更好的方法是创建某种 serialization/deserialization 例程。

此外,我不仅会使用 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);