【问题标题】:How to serialize structure data in C++?如何在 C++ 中序列化结构数据?
【发布时间】:2020-02-24 05:23:46
【问题描述】:

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

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

这是正确的吗?任何人都可以提供替代设计吗?我没有收到回电,所以我只是想了解我可以改进的地方。

【问题讨论】:

  • std::memcpy() 在 C 代码中?
  • 我的意思是一般的嵌入式 C/C++ 代码。现在编辑问题。
  • @DavidC.Rankin — htonl() 是否管理 float 数据的转换?
  • 鉴于tmp是一个指针,std::memcpy(&tmp, data, sizeof(tmp));是错误的;你需要sizeof(*tmp)
  • 一旦发布了 C++ 答案,请不要删除 C++ 语言标签。您的编辑现在使它们变得无关紧要。我将回滚更改。如果您只对 C 感兴趣,那么请提出一个新问题。

标签: c++ serialization


【解决方案1】:

这对吗?

很可能,不会。

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

a) 对预期格式的严格协议 - 例如如果它是某种文本(XML、JSON、CSV,...),或者它是“原始二进制”,每个字节的含义都有明确的定义(例如,也许“字节 1 始终是有效数字的最低 8 位” ")。

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

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

【讨论】:

    【解决方案2】:

    可能会有很多改进,但我建议您检查cereal,而不是告诉所有这些改进。它是广泛使用的序列化/反序列化库,因此考虑了很多关键点。

    我的一些想法是:

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

    2. char* encode(struct AMG_ANGLES *a)函数返回char*,可能是泄露了。为了防止这个问题,让std::unique_ptr<T> 决定它的生命周期或者用一个类来包装它。但是以某种方式摆脱指针。

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

      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

    【讨论】:

      【解决方案3】:

      有人可以在 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;
      }
      

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

      【讨论】:

        【解决方案4】:

        最好创建一些类,比如 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 };
        }
        
        

        【讨论】:

          猜你喜欢
          • 2010-09-27
          • 2021-09-21
          • 2016-05-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多