10

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

使用中最标准的工具是htonl来自 POSIX 的 et al。这些功能有缺点:

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

用于将任意大小序列化为char8 位标准字节的接口介于 C 标准之间,该标准并不真正承认 8 位字节,而任何标准(ITU?)都将八位字节设置为基本的传输单元。但是旧的标准没有得到修订。

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

这样的扩展会有用吗,还是担心非二进制补码机器就没有意义?

4

3 回答 3

6

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

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

本教程似乎是一个很好的介绍,您可以在此处阅读有关实际二进制存储格式的信息。


从他们的网页

什么是协议缓冲区?

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

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

我不知道在存在非 8 位字节的情况下它们的表现如何,但应该相对容易找出。

于 2012-07-24T01:50:22.147 回答
4

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

许多 CPU 无法(有效地)访问未存储在地址不是整数字节大小的倍数的内存位置的多字节整数。这就是永远不要使用结构覆盖来(反)序列化网络数据包的原因。我不确定这是否是您所说的“就地转换”。

我经常使用嵌入式系统,并且我在自己的库中使用了这些函数,我在生成或解析网络数据包(或任何其他 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)serialize 函数逐字节读取或写入值,因此不会发生对齐问题。您也不必担心签名。首先,如今所有系统都使用 2s 补码(可能除了一些 ADC,但您不会使用这些功能)。然而,它甚至应该在使用 1s 补码的系统上工作,因为(据我所知)有符号整数在转换为无符号时会转换为 2s 补码(并且函数接受/返回无符号整数)。

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

于 2012-07-23T13:56:10.707 回答
1

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

于 2012-07-23T14:12:03.743 回答