如果 struct 发生更改,如何将其读写到二进制文件中

3

我有以下结构体

struct MyStruct
{
    int     param1;
    float   param2;
    double  param3;
}

我可以使用以下方式将其写入二进制文件:

fstream binary_file(file, ios::out|ios::binary); 
binary_file.seekg(0, ios::beg);
binary_file.write((char *)aStruct,sizeof(MyStruct));
binary_file.close();

我可以使用以下方式进行恢复:

ifstream binary_file;
binary_file.open(file, ios::binary);
binary_file.seekg(0, ios::beg);
binary_file.read((char *)aStruct, sizeof(MyStruct));
binary_file.seekg (0, ios::end);
binary_file.close();

这一切都很顺利。现在更改结构的定义为:
struct MyStruct
{
    int     param1;
    float   param2;
    double  param3;
    int     paramA;
    float   paramB;
    double  paramC
}

问题是,如果我读取一个在定义更改之前编写的文件,param1、param2和param3是否总是被正确设置,并且我能确定paramA、paramB和paramC不会被分配任何垃圾?参数将只会添加到结构体的末尾。
根据参考文献,ifstream read函数应该在读取指定位数之前遇到eof时停止读取,所以希望这听起来就像它听起来那么简单。我的测试也表明,问题的答案是肯定的,但是我想确认一下,因为我一直在阅读关于二进制文件中填充的内容,不完全理解它的工作原理。

你测试过看结果是什么吗? - Ian Medeiros
1
我的测试结果是,param1、param2和param3被正确设置,而paramA、paramB和paramC的值分别为0、0.0f和0.0。然而,我只是想确保这不仅仅是巧合。 - joaerl
请查看谷歌的Protocol Buffers,以获取可以处理此类情况下结构版本控制的代码。 - Jonathan Leffler
2个回答

4

问题是,如果我读取一个在定义更改之前编写的文件,那么param1、param2和param3是否总是被正确设置?

是的,你可以确定这一点。

我能确定paramA、paramB和paramC不会被分配任何垃圾吗?

也是正确的,但有一个例外:除非这些字段在构造函数中初始化,否则在读取后它们将保持未初始化(即包含“垃圾”)。

最后,请注意,这个技巧只适用于从二进制文件中读取单个struct。如果你需要保存这样的struct数组,你必须存储写入数据时有效的sizeof,否则你将无法正确地分割数组。

显然,即使没有在struct末尾添加新字段,改变到不同的编译器或同一编译器的不同版本也可能破坏这个方案。


感谢您的快速回复。如果我没有为我的结构体(specify a constructor)指定一个构造函数,是否有一个“默认”构造函数将初始化成员变量?以下帖子似乎表明了这一点:https://dev59.com/IGsy5IYBdhLWcg3w2Bk6 - joaerl
我认为这不安全。如果新属性导致结构的填充发生变化,第一个属性可能会出现垃圾值。 - Mark B
1
@joaerl 这个链接中的答案说你会得到一个默认构造函数,但它也说,根据字段的类型,构造的字段可能保持未初始化状态。在你的情况下,这些字段肯定会保持未初始化状态,因为它们是基本的 C++ 类型。 - Sergey Kalinichenko
@MarkB 我认为标准不允许编译器在新添加的字段之前更改字段间的填充;只有旧字段中最后一个和新添加字段中第一个之间的填充可以更改。这种额外的填充没有任何影响,因为文件中没有数据会进入这个新的“间隙”。 - Sergey Kalinichenko
@dasblinkenlight 考虑一个略微不同的结构体:struct test { int a; int b; int c; }; 改为 struct test2 { int a; int b; int c; double d; };。你是在暗示编译器会在 test2 中使 d 未对齐吗? - Mark B
1
@MarkB 绝对不是这样!我的建议是,如果有的话,原始成员abc之间的间隙将保持不变。它们与d之间的间隙对读取数据的代码没有影响,因为在原始的struct中没有足够的数据来填充这个间隙。 - Sergey Kalinichenko

0

这可能适用于您,也可能不适用。

如果编译器在添加新属性时更改原始属性的填充,则将无法读取原始值。

如果填充(和字节序)未更改,则您很可能能够成功从文件中恢复单个记录,并且新属性中只有未初始化的数据。


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