纯C/C++中的二进制序列化

7

我希望自己实现二进制序列化,而不使用Boost或其他第三方库。

C++中最简单的方法是使用ofstream,然后通过网络发送二进制文件。但是否有其他流类可以用作临时缓冲区以避免将文件写入磁盘?

此外,在纯C中如何实现这一点?


2
你是否正在寻找std::stringstream - Florian Sowade
1
尽管二进制序列化通常意味着将对象和状态保存到磁盘中,但std::ostream可以用作缓冲区。 - AJG85
你说:“目标是‘完全理解这个过程,而不仅仅是调用/使用一些现成的东西’”,而你也知道 boost 做了你要找的东西... 为什么不简单地查看 boost 如何运作,这样你就能理解它的工作原理了? - mah
谷歌搜索“write”和“read”。 - David Rodríguez - dribeas
1
由于Boost设计并非为教育目的而开发,因此序列化块不够清晰。 - Secret
4个回答

18

持久化是一个难题。即使将对象序列化到磁盘上也不是一件简单的事情。假设你有一个C语言中的结构体如下:

struct Person {
    char name[100];
    int year;
};

这是一个自包含结构体,可能是实现序列化最简单的方式。然而,你将面临以下问题:

  1. 编译器的填充系统。为了使一个结构在内存中占用整数个字,完成结构的方式是不标准的。

  2. 操作系统和计算机本身以二进制形式表示数据的方式。显然,这种表示方法因计算机而异。

由此得出的结论是,即使是在同一操作系统中相同的程序创建的文件,在同一操作系统中使用相同的程序也可能不兼容,这是因为两个程序可能使用了不同的C编译器。

现在让我们来看一个C++对象:

class Person {
public:
    // more things...

private:
    string name;
    Date * birth;
    Firm * firm;
};
现在同样的事情变得非常复杂了。对象不再是自包含的,你需要跟随指针来决定如何处理每个对象(这被称为3.指针扫描和传递性持久化)。而且你仍然有第1和第2个问题。
因此,假设你专注于自包含的对象,并且仍然需要解决1和2这两个问题。唯一的方法是决定使用a)文本格式或b)字节码格式进行表示。任何操作系统中的任何程序都可以理解字节码格式,因为信息是按字节读取和写入的。这是Java或C#序列化它们的对象的方式。作为表示的文本格式与字节码一样有效,尽管较慢。它的主要优点是既可以被人类理解,也可以被计算机理解(结构化文本格式可以是XML)。
因此,为了序列化你的自包含对象,无论选择哪种输出格式,你都需要有基本函数(或在C++中是类),能够读取int、char、string等等。当你对每一个数据类型都有了写入和读取函数对时,你将不得不为程序员提供创建她自己的write/read对的可能性,使用你的read/write对用于元素数据。
我们在这里谈论关于完整的框架,类似于Python提供的它的pickle模块。
最后,能够缓存你的序列化而不是保存到磁盘的事实,是你最不用担心的问题。如果你使用基于文本的格式,可以使用ostringstream类;如果你使用字节码,则可以使用内存块。
正如你所看到的,这不是一项简单的工作。希望这有所帮助。

1
非常感谢您提供如此好的五星级答案。我之所以提出这样的问题,是因为我需要对用作服务器SSL消息的结构进行序列化。我正在尝试手动处理SSL,而不使用OpenSSL或其他工具,并且对自我二进制序列化也产生了兴趣!这就是我提出这个问题的原因。再次感谢! - Secret

3

我一直在使用JSON来序列化数据。它很简单,这是一个非常好的事情。很容易正确地使用JSON,并且很容易发现任何问题。

虽然它不像其他格式那样节省空间,但对于许多目的来说已经足够了。而且你可以从JSON网站获取免费的库代码。

http://json.org/


2
你可以使用纯C语言的Binn格式。
示例代码:
  binn *obj;

  // create a new object
  obj = binn_object();

  // add values to it
  binn_object_set_int32(obj, "id", 123);
  binn_object_set_str(obj, "name", "John");
  binn_object_set_double(obj, "total", 2.55);

  // send over the network or save to a file...
  send(sock, binn_ptr(obj), binn_size(obj));

  // release the buffer
  binn_free(obj);

免责声明:我是创作者。

他特别要求“不使用Boost或任何其他第三方库”。 - abyesilyurt

-7
在某些情况下,当处理简单类型时,您可以执行以下操作:
object o; 
socket.write(&o, sizeof(o)); 

8
由于结构体对齐问题,大多数编译器会在成员变量中填充空间以使它们对齐到特定的字节数倍数上。 参考链接:https://dev59.com/jnVD5IYBdhLWcg3wAWoO因此,如果这段代码是由一个平台上的编译器编译而成,并被另一个平台上的编译器或同一平台上的不同编译器所读取,则可能无法正常工作。该代码是跨平台的,但是它所生成的数据却不是。 - Jamin Grey
这会导致数据不正确对齐,即使是稍有不同的编译器也会如此。此外,在某些情况下,数据在不同的架构、编译器甚至操作系统之间也无法移植。-1 - Michael J. Gray
尽管我不太喜欢使用编译指示,但我认为“pragma pack”在很大程度上解决了这个问题。我相信它被GCC、clang、ICC和MSVC支持,所以使用它可能是合理的。我提出这个问题的原因是,它是一个好东西,值得了解它是什么以及为什么需要它。此外,由于这个答案没有展示结构体是否使用了pragma pack,我们无法确定它是否有效。这段代码有可能是有效的。 - Christopher Mauer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接