简体   繁体   English

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

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

Currently we use packed structs with bitfields to create our datas目前我们使用带位域的打包结构来创建我们的数据

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

It does work because we use the same cpu architecture everywhere and gcc is kind enough to allow us to use members of the AllDatas union that are not the one we last wrote.它确实有效,因为我们在任何地方都使用相同的 cpu 架构,而 gcc 足以让我们使用不是我们上次编写的 AllDatas 联合的成员。

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

But:但:

  • It's undefined behavior if we follow the standard specification如果我们遵循标准规范,这是未定义的行为
  • It's heavily platform dependant.它严重依赖于平台。
  • It's rather messy and ineficient它相当混乱和低效

But it offer the advantage to allow us to check at compile time that our structs have the size that we expect and hence, won't compile if that's not the case (work on 32 bits platform but not on a 64 one for example)但它提供的优势是允许我们在编译时检查我们的结构是否具有我们期望的大小,因此,如果不是这种情况则不会编译(例如在 32 位平台上工作,但不能在 64 位平台上工作)

How can I create serialization functions that will ensure that at least, my output buffer has the size expected and that I did not forgot to add some members or on the contrary put one twice for example?我如何创建序列化函数,以确保至少我的 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;
}

The serialization format is clearly defined so I can't switch to a serialization library given that they can't (to my knowledge) be so tightly packed and so won't follow our format from the start.序列化格式已明确定义,因此我无法切换到序列化库,因为它们(据我所知)不能如此紧密地打包,因此从一开始就不会遵循我们的格式。

As a side question, is there a way to avoid the double type declaration of the array (in the return type and in the function body)?作为附带问题,有没有办法避免数组的双精度类型声明(在返回类型和 function 主体中)? I found no way to create a variable of the return type of the function in the function itself?我发现没有办法在 function 本身中创建 function 的返回类型的变量?

I finally managed to get something that can check at compile time not only that the size is right but that my serialization and deserialization functions work as expected as well我终于设法得到一些东西,不仅可以在编译时检查大小是否正确,而且我的序列化和反序列化函数也能按预期工作

serializer.h序列化器.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);
}

serializer.cpp序列化器.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");

With the out of bound checks of the array made at compile time, the assert to ensure that we filled the entire buffer and the comparison made in checkData1() I can be confident that my serialization and deserialization functions work.通过在编译时对数组进行越界检查、确保我们填充了整个缓冲区的断言以及在 checkData1() 中进行的比较,我可以确信我的序列化和反序列化函数可以正常工作。

I think that I will just move all that to unit tests to avoid the compilation time overhead and all the constexpr that are required for every function involved in the consteval check but it's nice to know that it's possible.我想我会把所有这些都移到单元测试中,以避免编译时间开销和 consteval 检查中涉及的每个 function 所需的所有 constexpr,但很高兴知道这是可能的。

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

相关问题 我可以确保在编译期间只创建一次对象吗? - Can I ensure object created only once during compile time? 如果在编译时大小未知,如何在堆栈上分配数组? - how can I allocate an array on the stack if the size is not known at compile time? 如何创建模板具有特定类型的编译时断言? - How can I create a compile time assertion that a template is of specific types? 为什么班级的大小为零? 如何确保不同的对象具有不同的地址? - Why is the size of my class zero? How can I ensure that different objects have different address? 协议缓冲区如何支持std容器的序列化/反序列化? - How can protocol-buffers support serialization/deserialization of std containers? 如何使这个涉及浮点函数的表达式成为编译时常量? - How can I make this expression involving floating-point functions a compile-time constant? 如何在编译时关闭功能? - How can I gate features at compile time? 如何在编译时“填充”大小的剩余部分? - How do I 'fill up' remainder of a size at compile time? 使用 C++17,我如何创建编译时 map 类型的值? - Using C++17, how can I create a compile time map of types to value? 如何在编译时创建 SQLlite 表并在 QT 中插入数据? - How can I Create a SQLlite table and insert data in it in QT at Compile time?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM