繁体   English   中英

我如何确保在编译时我的序列化函数创建具有预期大小的缓冲区?

[英]How can I ensure at compile time that my serialization functions create buffers with the expected size?

目前我们使用带位域的打包结构来创建我们的数据

struct Data1
{
   uint8_t type;
   uint8_t value;
   bool aBool:1;
   uint8_t threeBits:3;
   uint8_t spare:4;
} __attribute__((packed))
const uint8_t Data1SerializedSize{3};
static_assert(sizeof(Data1) == Data1SerializedSize, "wrong Data1 size");

...

const uint8_t MaxBufferSize{32};

union AllDatas
{
   Data1 data1;
   ...
   uint8_t buffer[MaxBufferSize];
}__attribute__((packed))
static_assert(sizeof(AllDatas) <= MaxBufferSize);

它确实有效,因为我们在任何地方都使用相同的 cpu 架构,而 gcc 足以让我们使用不是我们上次编写的 AllDatas 联合的成员。

void send(Serial& serial, const Data1& data)
{
    AllDatas u;
    u.data1 = data;
    serial.write(u.buffer, Data1SerializedSize);
}

但:

  • 如果我们遵循标准规范,这是未定义的行为
  • 它严重依赖于平台。
  • 它相当混乱和低效

但它提供的优势是允许我们在编译时检查我们的结构是否具有我们期望的大小,因此,如果不是这种情况则不会编译(例如在 32 位平台上工作,但不能在 64 位平台上工作)

我如何创建序列化函数,以确保至少我的 output 缓冲区具有预期的大小,并且我没有忘记添加一些成员,或者相反,例如将一个成员放两次?

std::array<uint8_t, Data1SerializedSize> serialize(const Data1& data)
{
   std::array<uint8_t, Data1SerializedSize> buffer;
   uint8_t head{0};
   buffer[head] = data.type;
   ++head;
   buffer[head] = data.size;
   ++head;
   buffer[head] = (data.aBool & 0x1) 
                | ((data.threeBits << 1) & 0b1110) 
                | ((data.spare << 4) & 0b11110000);
   ++head;
   //static_assert(head == Data1SerializedSize);//won't work and above code looks more error prone
   return buffer;
}

序列化格式已明确定义,因此我无法切换到序列化库,因为它们(据我所知)不能如此紧密地打包,因此从一开始就不会遵循我们的格式。

作为附带问题,有没有办法避免数组的双精度类型声明(在返回类型和 function 主体中)? 我发现没有办法在 function 本身中创建 function 的返回类型的变量?

我终于设法得到一些东西,不仅可以在编译时检查大小是否正确,而且我的序列化和反序列化函数也能按预期工作

序列化器.h

struct Data1Serializer
{
   static const uint8_t size{3};
   constexpr std::array<uint8_t, size> serialize(const Data1& data);
   constexpr Data1 deserialize(const std::array<uint8_t, size>& buffer);
}

序列化器.cpp

constexpr auto
Data1Serializer::serialize(const Data1& data)
-> std::array<uint8_t, size>
{
   std::array<uint8_t, size> buffer;
   uint8_t head{0};
   buffer[head] = data.type;
   ++head;
   buffer[head] = data.value;
   ++head;
   buffer[head] = (data.aBool & 1)
            | ((data.threeBits >> 1) & 0b111)
            | ((data.spare >> 4) & 0b1111));
   ++head;
   assert(head == size);
   return buffer;
}

constexpr auto
Dat1Serializer::deserialize(const std::array<uint8_t, size>& buffer)
-> Data1
{
   Data1 data;
   data.type = buffer[0];
   data.value = buffer[1];
   data.aBool = buffer[2] & 1;
   data.threeBits = (buffer[2] >> 1) & 0b111;
   data.spare = (buffer[2] >> 4) & 0b1111;
   return data;
}

consteval bool checkData1()
{
   Data1 data{3,2,true,5,0};
   Data1Serializer serializer;
   const auto& result = serializer.deserialize(serializer.serialize(data));
   
   return (data.type == result.type)
       && (data.value == result.value)
       && (data.aBool == result.aBool)
       && (data.threeBits == result.threeBits)
       && (data.spare == result.spare);
}

static_assert(checkData1() == true, "Data1 serialization or deserialization failure");

通过在编译时对数组进行越界检查、确保我们填充了整个缓冲区的断言以及在 checkData1() 中进行的比较,我可以确信我的序列化和反序列化函数可以正常工作。

我想我会把所有这些都移到单元测试中,以避免编译时间开销和 consteval 检查中涉及的每个 function 所需的所有 constexpr,但很高兴知道这是可能的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM