如何在C ++中序列化结构数据?

问题描述 投票:-1回答:4

我在一次采访中被要求对数据进行序列化(这样就可以将其存储在缓冲区中并通过某些网络发送)。这是我想出的-

struct AMG_ANGLES {
    float yaw;
    float pitch;
    float roll;
};

char b[sizeof(struct AMG_ANGLES)];

char* encode(struct AMG_ANGLES *a)
{

    std::memcpy(b, &a, sizeof(struct AMG_ANGLES));
    return b;
}

void decode(char* data)
{
 // check endianess   
    AMG_ANGLES *tmp; //Re-make the struct
    std::memcpy(&tmp, data, sizeof(tmp));
}

这是正确的吗?谁能提供替代设计?我没有得到回调,所以我只是想学习我可以改进的地方。

c++ serializer
4个回答
6
投票

这是正确的吗?

最有可能,没有。

序列化的目的是将数据转换为完全独立于平台的形式-例如不依赖于字节序或float是IEEE 754或其他非常不同的东西。这需要:

a)关于预期格式的严格协议-例如如果是某种文本(XML,JSON,CSV等),或者是“原始二进制文件”,其中包含每个字节含义的明确定义(例如,例如“字节1始终是有效字节的最低8位” “)。

b)正确转换为任何预期格式(例如,确保字节1始终是有效位数的最低8位,而不考虑任何/所有平台差异)

但是;至少从技术上讲,该代码不应该是可移植的,并且该规范(“预期格式的协议”)恰好与您最终为该代码所针对的唯一平台匹配的内容;因此至少从技术上讲,代码是正确的。


0
投票

可能会有很多改进,但是建议您不要研究所有改进,而建议研究cereal。它是广泛使用的序列化/反序列化库,因此需要考虑许多要点。

我的一些想法是:

  1. 您的代码取决于alignmentendianness所运行的硬件。因此,序列化的数据不可移植,并且依赖于编译器。

  2. char* encode(struct AMG_ANGLES *a)函数返回char*,可能已泄漏。为防止出现此问题,请让std::unique_ptr<T>决定其生存期或将其包装为类。但是以某种方式摆脱了指针。

  3. Templatize序列化/反序列化操作。否则,您可以为其他类型编写相同的函数。

    template<typename T>
    char* encode( T* a ) // I leave signature as is, just to demonstrate
    {
         std::memcpy( b , &a , sizeof(T) );
         return b;
    }
    
  4. 如果格式由您决定,则最好选择可读的格式,而不是二进制归档,例如JSONXML

0
投票

有人可以用C语言提供替代设计吗?

“标准”方法是使用printfscanf创建数据的ascii表示形式:

#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <assert.h>
#include <float.h>

struct AMG_ANGLES {
    float yaw;
    float pitch;
    float roll;
};

// declare a buffer at least this long to be sure encode works properly
#define AMG_ANGLES_BUFSIZE  ( \
    3 * ( /* 3 floats */ \
         2 + /* digit and dot */ \
         FLT_DECIMAL_DIG - 1 + /* digits after dot */ \
         4 /* the 'e±dd' part */ \
    ) \
    + 2 /* spaces */ \
    + 1 /* zero terminating character */ \
)

int encode(char *dest, size_t destsize, const struct AMG_ANGLES *a) {
    return snprintf(dest, destsize, "%.*e %.*e %.*e", 
         FLT_DECIMAL_DIG - 1, a->yaw, 
         FLT_DECIMAL_DIG - 1, a->pitch, 
         FLT_DECIMAL_DIG - 1, a->roll);
    // my pedantic self wants to add `assert(snprintf_ret < AMG_ANGLES_BUFSIZE);`
}

int decode(struct AMG_ANGLES *dest, const char *data) {
    return sscanf(data, "%e %e %e", &dest->yaw, &dest->pitch, &dest->roll) == 3 ? 0 : -1;
}

int main() {
   char buf[AMG_ANGLES_BUFSIZE];
   const struct AMG_ANGLES a = { FLT_MIN, FLT_MAX, FLT_MIN };
   encode(buf, sizeof(buf), &a);
   struct AMG_ANGLES b;
   const int decoderet = decode(&b, buf);
   assert(decoderet == 0);
   assert(b.yaw == FLT_MIN);
   assert(b.pitch == FLT_MAX);
   assert(b.roll == FLT_MIN);
}

但是在裸机嵌入式中,我尽量不使用scanf-这是一个很大的功能,具有某些依赖性。因此,最好自己调用strtof,但需要一些思考:

int decode2(struct AMG_ANGLES *dest, const char *data) {
    errno = 0;

    char *endptr = NULL;
    dest->yaw = strtof(data, &endptr);
    if (errno != 0 || endptr == data) return -1;
    if (*endptr != ' ') return -1;

    data = endptr + 1;
    dest->pitch = strtof(data, &endptr);
    if (errno != 0 || endptr == data) return -1;
    if (*endptr != ' ') return -1;

    data = endptr + 1;
    dest->roll = strtof(data, &endptr);
    if (errno != 0 || endptr == data) return -1;
    if (*endptr != '\0') return -1;

    return 0;
}

或带有删除的代码重复项:

int decode2(struct AMG_ANGLES *dest, const char *data) {
    // array of pointers to floats to fill
    float * const dests[] = { &dest->yaw, &dest->pitch, &dest->roll };
    const size_t dests_cnt = sizeof(dests)/sizeof(*dests);
    errno = 0;
    for (int i = 0; i < dests_cnt; ++i) {
        char *endptr = NULL;
        *dests[i] = strtof(data, &endptr);
        if (errno != 0 || endptr == data) return -1;
        // space separates numbers, last number is followed by zero
        const char should_be_char = i != dests_cnt - 1 ? ' ' : '\0';
        if (*endptr != should_be_char) return -1;
        data = endptr + 1;
    }
    return 0;
}

我需要使用一些google并重新阅读chux的答案,以正确地回忆起如何在FLT_DECIMAL_DIG中使用printf打印浮点数,这很可能是因为我很少使用浮点数。


-1
投票

最好制作一些类似std :: stringstream的类。std :: stringstream不能很好地保存二进制数据,但是它的运行方式与您想要的相同。因此我可以举一个与std :: stringstream ..]一起使用的示例

此代码实现仅用于序列化,但还添加了反序列化的代码。

// C++11
template < typename T, typename decltype(std::declval<T>().to_string())* = nullptr>
    std::ostream& operator<< (std::ostream& stream, T&& val)
{
    auto str = val.to_string();
    std::operator <<(stream, str);
    return stream;
}

struct AMG_ANGLES {
    float yaw;
    float pitch;
    float roll;
    std::string to_string() const
    {
        std::stringstream stream;
        stream << yaw << pitch << roll;
        return stream.str();
    }
};

void Test()
{
    std::stringstream stream;
    stream << 3 << "Hello world" << AMG_ANGLES{1.f, 2.f, 3.f };
}

© www.soinside.com 2019 - 2024. All rights reserved.