c++ 将char * buffer转换为cpp中的struct

omqzjyyz  于 2023-06-07  发布在  其他
关注(0)|答案(3)|浏览(202)

我是c和cpp的新手,我想知道是否有一种方法可以将char * buffer转换为struct,考虑到它可以在字节数组中容纳多个0x 00。我有一个结构头,我包括在我的cpp文件。

struct ExampleStruct{
    uint8_t id;
    uint16_t message;
    int8_t param1;
    int16_t param2;
    int8_t param3;
    int8_t param4;
}

在我的cpp文件中,我有一个udp服务器,它在char * buffer中接收N个字节;缓冲区可能看起来像这样:

char buffer[] = {0x0f, 0xff, 0x00, 0xd4, 0xff, 0x00, 0x00, 0xff};

我试着这样做:

struct ExampleStruct exampleStruct;
memcpy(&exampleStruct, buffer, bytesReceived);

还有这个

struct ExampleStruct *exampleStruct = (ExampleStrcut *)buffer;

,但是当我尝试以十六进制输出例如exampleStruct.param4时,它们都不起作用,它显示了其他内容。另外,我无法控制缓冲区消息的发送。
我怎么把它转换成一个结构体呢,我试着把它强制转换,但是由于某种原因,它强制转换不正确,我觉得这可能是由于字节数组中有0x 00。请帮我把它转换成收到的N字节

wsewodh2

wsewodh21#

恕我直言,更安全的方法是逐个字段或逐个成员复制。此技术允许您插入成员的转换(例如小端到大端)。

struct ExampleStruct
{
    uint8_t id;
    uint16_t message;
    int8_t param1;
    int16_t param2;
    int8_t param3;
    int8_t param4;

    void copy_from_buffer(uint8_t const * p_buffer)
    {
       id = *p_buffer;    p_buffer += sizeof(id);

       message = *((uint16_t *)(p_buffer));
       p_buffer += sizeof(message);

       param1 = *p_buffer; ++p_buffer;

       param2 = *((uint16_t *)(p_buffer));
       p_buffer += sizeof(param2);

       param3 = *p_buffer; ++p_buffer;
       param4 = *p_buffer; ++p_buffer;
    }
};

这消除了结构中的填充问题,因此不需要非标准的pack杂注。
(Note:因为OP标记了C++,所以我使用了成员函数。)
由于选择了 C++ 标记,因此可以轻松地将其调整为简单的序列化接口:

class Serialize
{
    public:
        virtual void copy_from_buffer(uint8_t const * & p_buffer) const = 0;
        virtual size_t  size_in_buffer() const = 0;
        virtual void copy_to_buffer(uint8_t * & p_buffer) const = 0;
};

通过按引用传递缓冲区指针,您可以通过传递相同的指针来“链接”调用。这允许在structclass中轻松集成非POD成员。
size_in_buffer方法用于分配目标缓冲区,因为缓冲区的大小可能与结构的大小不同(由于填充、对齐等原因)。

f2uvfpb9

f2uvfpb92#

为了在可移植代码中实现这一点,通常必须显式地处理转换,一次一个,并且通常希望将原始数据读入unsigned char数组,以便可以轻松/安全地使用它进行位游戏。
当我做这样的事情时,我通常定义一个byte类,以便于将字节写成无符号数(uint8_t通常是unsigned char的别名,因此它们会打印为字符)。
我还定义了一个小模板函数来从字节流中读取大端整数。
对于这个结构体来说,这可能有点大材小用,但是如果您正在进行网络通信,您可能最终会使用它们的更多用途。
把这些放在一起,代码看起来像这样:

#include <iostream>

class byte {
    unsigned char val;
public:
    byte(unsigned char val) : val(val) {}

    friend std::ostream &operator<<(std::ostream &os, byte b) {
        return os << static_cast<uint16_t>(b.val & 0xff);
    }
};

template <class T>
T BigEndian(unsigned char const* buffer)
{
    T ret = 0;
    for (unsigned i = 0; i < sizeof(T); i++) {
        T temp = static_cast<T>(buffer[i]);
        ret <<= CHAR_BIT;
        ret += temp;
    }
    return ret;
}

struct ExampleStruct {
    byte id;
    uint16_t message;
    byte param1;
    int16_t param2;
    byte param3;
    byte param4;

    ExampleStruct(unsigned char (&array)[8])
        : id(array[0])
        , message(BigEndian<uint16_t>(array+1))
        , param1(array[3])
        , param2(BigEndian<uint16_t>(array+4))
        , param3(array[6])
        , param4(array[7])
    { }

    friend std::ostream &operator<<(std::ostream &os, ExampleStruct const &e) {
        return os << std::hex << "id: " << e.id
                  << ", message: " << e.message
                  << ", param1: " << e.param1
                  << ", param2: " << e.param2
                  << ", param3: " << e.param3
                  << ", param4: " << e.param4;
    }
};

int main() {
    unsigned char buffer[] = { 0x0f, 0xff, 0x00, 0xd4, 0xff, 0x00, 0x00, 0xff };

    ExampleStruct s(buffer);

    std::cout << s << "\n";
}

结果:

id: f, message: ff00, param1: d4, param2: ff00, param3: 0, param4: ff

(这似乎是我所期望的)。
当然,如果您的uint16_t项是little-endian(对于网络数据来说不常见,但肯定是可能的),则可以使用LittleEdian<uint16_t>(这显然会以little-endian顺序处理字节)。

ddhy6vgd

ddhy6vgd3#

有两件事可以阻止memcpy解决方案的工作。
1.对齐和填充。每个uint16_t地址必须能被2整除(在大多数情况下),因此C++为结构体添加了填充,使每个uint16_t字段对齐2个字节:

struct ExampleStruct{
    uint8_t id;
    uint8_t __padding0;
    uint16_t message;
    int8_t param1;
    uint8_t __padding1;
    int16_t param2;
    int8_t param3;
    uint8_t __padding2;
};

为了避免这种情况,你可以打包你的struct:

#pragma pack(push, 1)
struct ExampleStruct{
    uint8_t id;
    uint16_t message;
    int8_t param1;
    int16_t param2;
    int8_t param3;
};
#pragma pack(pop)

// Use it like this:
struct ExampleStruct exampleStruct;
memcpy(&exampleStruct, buffer, sizeof(ExampleStruct));

请注意,我使用sizeof(ExampleStruct)是因为您可能会收到比结构体中更多的字节,在这种情况下,您将以将不受信任的数据复制到堆栈中结束,这可能导致严重的漏洞,如远程代码执行。
第二个例子在C中是不允许的,因为C标准没有定义类型双关。
1.考虑编码数据时使用的字节序。这是很有可能的,它是在编码的消息,但您的计算机有小端整数,所以每个整数值将有字节反转。

相关问题