便携式C二进制序列化原语

据我所知,C库没有帮助将数值序列化为非文本字节流。 如我错了请纠正我。

使用的最标准工具是来自POSIX的htonl等人。 这些function有缺点:

  • 没有64位支持。
  • 没有浮点支持。
  • 签名类型没有版本。 反序列化时,无符号到符号的转换依赖于有符号整数溢出,即UB。
  • 它们的名称没有说明数据类型的大小。
  • 它们依赖于8位字节和精确大小的uint_ N _t的存在。
  • 输入类型与输出类型相同,而不是引用字节流。
    • 这要求用户执行指针类型转换,这可能在对齐时不安全。
    • 执行该类型转换后,用户可能会尝试在其本机内存布局中转换和输出结构,这种做法很糟糕,导致意外错误。

用于将任意大小的char化为8位标准字节的接口将落在C标准之间,该标准不真正确认8位字节,并且无论标准(ITU?)将八位位组设置为基本传输单位。 但旧的标准没有得到修订。

现在C11有许多可选组件,可以添加二进制序列化扩展以及线程之类的东西,而不需要对现有实现提出要求。

这样的扩展是否有用,或者担心非二进制补充机器是否毫无意义?

我从未使用它们,但我认为Google的Protocol Buffers满足您的要求。

  • 支持64位类型,有符号/无符号和浮点类型。
  • 生成的API是类型安全的
  • 可以对流进行序列化

本教程似乎是一个非常好的介绍 ,您可以在这里阅读有关实际的二进制存储格式。


从他们的网页 :

什么是协议缓冲?

协议缓冲区是Google的语言中立,平台中立,可扩展的机制,用于序列化结构化数据 – 思考XML,但更小,更快,更简单。 您可以定义数据的结构化时间,然后使用特殊生成的源代码,可以使用各种语言(Java,C ++或Python)轻松地在各种数据流中编写和读取结构化数据。

纯C(仅限C ++)中没有正式实现,但有两个C端口可能满足您的需求:

  • Nanopb, http: //koti.kapsi.fi/jpa/nanopb/

  • Protobuf-c, url为http://code.google.com/p/protobuf-c/

我不知道它们是否存在非8位字节,但它应该相对容易找到。

在我看来,像htonl()这样的函数的主要缺点是它们只执行序列化工作的一半。 如果你的机器是小端,它们只会翻转多字节整数中的字节。 序列化时必须完成的另一件重要事情是处理对齐,而这些函数不会这样做。

许多CPU不能(有效地)访问未存储在存储器位置的多字节整数,该存储器位置的地址不是整数字节大小的倍数。 这是永远不会使用struct overlays(de)序列化网络数据包的原因。 我不确定这是否是’就地转换’的意思。

我在嵌入式系统上工作很多,而且我在自己的库中运行,在生成或解析网络数据包(或任何其他I / O:磁盘,RS232等)时我总是使用它:

 /* Serialize an integer into a little or big endian byte buffer, resp. */ void SerializeLeInt(uint64_t value, uint8_t *buffer, size_t nrBytes); void SerializeBeInt(uint64_t value, uint8_t *buffer, size_t nrBytes); /* Deserialize an integer from a little or big endian byte buffer, resp. */ uint64_t DeserializeLeInt(const uint8_t *buffer, size_t nrBytes); uint64_t DeserializeBeInt(const uint8_t *buffer, size_t nrBytes); 

除了这些函数之外,还有一些宏定义如下:

 #define SerializeBeInt16(value, buffer) SerializeBeInt(value, buffer, sizeof(int16_t)) #define SerializeBeUint16(value, buffer) SerializeBeInt(value, buffer, sizeof(uint16_t)) #define DeserializeBeInt16(buffer) DeserializeBeType(buffer, int16_t) #define DeserializeBeUint16(buffer) DeserializeBeType(buffer, uint16_t) 

(de)序列化函数逐字节读取或写入值,因此不会出现对齐问题。 您也不必担心签名。 首先,所有系统现在使用2s补码(除了几个ADC,但是你不会使用这些function)。 然而,它甚至应该在使用1s补码的系统上工作,因为(据我所知)有符号整数在转换为无符号时被转换为2s补码(并且函数接受/返回无符号整数)。

你的另一个论点是它们依赖于8位字节和精确大小的uint_N_t的存在。 这也是我的function,但在我看来这不是一个问题(这些类型总是为我使用的系统及其编译器定义)。 您可以调整函数原型以使用unsigned char而不是uint8_t ,如果您愿意,可以使用long longuint_least64_t而不是uint64_t

请参阅xdr库和XDR标准RFC-1014 RFC-4506