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

时间:2012-07-16 08:15:32

标签: c portability binaryfiles endianness htonl

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

最常用的标准工具是POSIX的htonl等。这些功能有缺点:

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

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

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

这样的扩展是否有用,或者担心非二合一机器是否毫无意义?

3 个答案:

答案 0 :(得分:6)

我从未使用过它们,但我认为Google的Protocol Buffers符合您的要求。

  • 64位类型,有符号/无符号和浮点类型为all supported
  • 生成的API是typesafe
  • 可以对流进行序列化

本教程seems like a pretty good introduction,您可以阅读有关实际二进制存储格式here的内容。


来自web page

  

什么是协议缓冲区?

     

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

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

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

答案 1 :(得分:4)

在我看来,像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,但是你不会使用这些函数)。然而它甚至应该在使用1s补码的系统上工作,因为(据我所知)有符号整数在转换为无符号时被转换为2s补码(并且函数接受/返回无符号整数)。

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

答案 2 :(得分:1)

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