将字节数组反序列化为结构体

5

我收到了一个由字符/字节组成的网络传输。它包含一个头部和一些数据。我想将头部映射到一个结构体上。以下是一个示例:

#pragma pack(1)

struct Header
{
    unsigned short bodyLength;
    int msgID;
    unsigned short someOtherValue;
    unsigned short protocolVersion;
};

int main()
{
    boost::array<char, 128> msgBuffer;
    Header header;

    for(int x = 0; x < sizeof(Header); x++)
        msgBuffer[x] = 0x01; // assign some values

    memcpy(&header, msgBuffer.data(), sizeof(Header));

    system("PAUSE");    

    return 0;
}

这个方法是否总是有效,假设结构体中从不包含任何可变长度字段?有没有跨平台/惯用的方法来实现这一点?
注意:
我在互联网上看到了很多库,可以让你进行序列化/反序列化,但我认为它们只能反序列化先前使用相同库序列化的内容。好吧,我对传输格式没有控制权。我肯定会得到一个字节数组/字符数组,其中所有值都紧随其后。
6个回答

5

一些处理器要求某些类型的正确对齐,否则将不接受指定的打包方式并生成硬件陷阱。

即使在常见的x86平台上,打包结构也会导致代码运行更慢。

另外,在使用不同字节序的平台时需要注意。

顺便说一下,如果您想使用绑定到多种编程语言的简单且跨平台的通信机制,请看看YAMI


5

仅仅进行简单的复制很可能会出现错误,至少如果数据来自于与你所在的架构(甚至只是编译器)不同的地方。这是由于以下原因:

第二个链接是GCC特有的,但这适用于所有编译器。

我建议逐字节读取字段,并从这些字节中组装更大的字段(int等)。这可以让您控制字节序和填充。


2
#pragma pack(1)指令在大多数编译器上都可以使用,但你可以通过计算数据结构的大小(如果我的数学没错的话是10),然后使用printf("%d", sizeof(Header));来检查是否正在进行打包。
正如其他人所说,如果你在不同的架构之间进行转换,仍需注意字节序。

1

我强烈反对逐字节阅读的想法。如果你在结构体声明中小心处理结构体打包,就可以毫无问题地将其复制到结构体中。至于字节序问题,逐字节读取可以解决问题,但不能为您提供通用解决方案。那种方法非常差劲。我之前为类似的工作做过类似的事情,并且没有出现任何问题。

想想这个。我有一个结构体,我还有相应的结构体定义。您可以手动构建它,但我已经编写了解析器并将其用于其他事情。

例如,上面给出的结构的定义是“sis”。(s = short,i = int)然后我将结构地址、此结构的定义和结构体打包选项提供给一个处理字节序问题的特殊函数,然后就完成了。

SwitchEndianToBig(&header, “sis”,4); // 4 = 结构打包选项


1
告诉我如果我错了,但据我所知,这样做将确保您的数据是正确的 - 假设在不同平台上类型具有相同的大小
#include <array>
#include <algorithm>

//#pragma pack(1) // not needed

struct Header
{
    unsigned short bodyLength;
    int msgID;
    unsigned short someOtherValue;
    unsigned short protocolVersion;
    float testFloat;

    Header() : bodyLength(42), msgID(34), someOtherValue(66), protocolVersion(69), testFloat( 3.14f ) {}
};

int main()
{
    std::tr1::array<char, 128> msgBuffer;
    Header header;

    const char* rawData = reinterpret_cast< const char* >( &header );

    std::copy( rawData, rawData + sizeof(Header), msgBuffer.data()); // assuming msgBuffer is always big enough

    system("PAUSE");    

    return 0;
}

如果你的目标平台上类型不同,你必须使用别名(typedef)为每个类型来确保所使用的每个类型的大小相同。


嗯,你正在走另一条路(将结构体转换为字节数组)。即使这样做也不完全有效,因为你正在将结构体的填充复制到数组中。 - drby

0

我知道我正在与谁通信,所以我不需要太担心字节序问题。但无论如何,我喜欢远离特定于编译器的命令。

那么这个怎么样:

const int kHeaderSizeInBytes = 6;

struct Header
{
    unsigned short bodyLength;
    unsigned short msgID;
    unsigned short protocolVersion; 

    unsigned short convertUnsignedShort(char inputArray[sizeof(unsigned short)])
        {return (((unsigned char) (inputArray[0])) << 8) + (unsigned char)(inputArray[1]);}

    void operator<<(char inputArray[kHeaderSizeInBytes])
    {
        bodyLength = convertUnsignedShort(inputArray);
        msgID = convertUnsignedShort(inputArray + sizeof(bodyLength));
        protocolVersion = convertUnsignedShort(inputArray + sizeof(bodyLength) + sizeof(msgID));
    }
};

int main()
{
    boost::array<char, 128> msgBuffer;
    Header header;

    for(int x = 0; x < kHeaderSizeInBytes; x++)
        msgBuffer[x] = x;

    header << msgBuffer.data();

    system("PAUSE");    

    return 0;
}

去掉pragma,但它并不像我想的那么通用。每次添加一个头字段时,你都要修改<<函数。你能以某种方式迭代结构体字段、获取字段类型并调用相应的函数吗?


关于迭代结构体字段:你必须使用一个结构体吗?我问这个问题是因为用元组来代替它会允许字段的迭代。 - Éric Malenfant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接