10
|
Mawg says reinstate Monica · 技术社区 · 15 年前 |
1
11
我确实有一些代码,如果需要,较长的字符串由两个较短的段拼凑在一起。讨厌。以下是我在保持某些数据兼容12年后的经验: 定义你的目标 -有两种:
向版本0添加版本支持 -至少写一个版本头。再加上保留(可能很多)旧的读卡器代码,就可以原始地解决第一种情况。如果您不想实现案例2,开始拒绝新数据 马上 ! 如果您只需要案例1,并且随着时间的推移,预期的更改相当小,那么您就可以进行设置。不管怎样,在第一次发布之前做的这两件事可以帮你以后减轻许多头痛。 序列化期间转换 -在运行时,只将数据保存在内存中的“新格式”。在持久性限制下进行必要的转换和测试(在读取时转换为最新的,在写入时实现向后兼容性)。这将版本问题隔离在一个地方,有助于避免难以跟踪的错误。 保留一组来自周围所有版本的测试数据。 存储可用类型的子集 -将实际序列化的数据限制为几个数据类型,如int、string、double。在 最 在这种情况下,额外的存储大小由支持这些类型更改的代码大小减少所组成。(不过,在嵌入式系统上,这并不总是一种权衡)。 例如,不要存储小于本机宽度的整数。(你 可以 当需要存储长整型数组时需要这样做)。 添加断路器 -存储一些允许您故意使旧代码显示错误消息的密钥,表明此新数据不兼容。您可以使用一个字符串作为错误消息的一部分-那么您的旧版本可能会显示一条它不知道的错误消息-“您可以使用convertx工具从我们的网站导入此数据”在本地化应用程序中不是很好,但仍然优于 “UNG_¼ltiges格式” . 不直接序列化结构 -这就是逻辑/物理分离。我们将两者结合起来,既有利弊。所有这些都不能在没有运行时开销的情况下实现,这在很大程度上限制了您在嵌入式环境中的选择。无论如何,在持久化期间不要使用固定的数组/字符串长度,这应该已经解决了一半的问题。 (a)适当的序列化机制 -我们使用一个双元序列化程序,它允许在存储时启动一个“块”,它有自己的长度头。读取时,会跳过额外的数据,默认情况下会初始化丢失的数据(这样可以在序列化J代码中大大简化“读取旧数据”的实现)。可以嵌套块。这就是你在身体方面所需要的,但在日常工作中需要一些糖衣。
(b)使用不同的内存表示法
-内存中的重复基本上可以是
我最初是这样写的,这样人们就不会问我每一个格式兼容性问题,虽然实现有很多缺点(我希望我能以今天的清晰认识到这个问题…)但它可以解决。 默认情况下,查询一个不存在的值将返回一个默认/零初始化值。当您在访问数据和添加新数据时记住这一点时,这有很大帮助:假设版本1会自动计算“foo长度”,而在版本2中,用户可以覆盖该设置。“计算类型”或“长度”中的值为零意味着“自动计算”,并且您已设置。 以下是您可以预期的“更改”方案:
为了实施案例2,您还需要考虑:
噢。真是太多了。但这并不像看上去那么复杂。 |
2
4
人们使用的关系数据库有一个巨大的概念。 它被称为将架构分解为“逻辑”和“物理”层。 你的结构是一个逻辑层和一个物理层,混合在一起成为一个难以改变的东西。 您希望您的程序依赖于一个逻辑层。您希望逻辑层依次映射到物理存储。这使您可以在不破坏事物的情况下进行更改。 您不需要重新设计SQL来实现这一点。 如果您的数据完全存储在内存中,那么考虑一下这个问题。将物理文件表示与内存表示分离。以一些“通用的”、灵活的、易于解析的格式(如JSON或YAML)编写数据。这允许您以通用格式读取,并构建高度特定于版本的内存结构。 如果将数据同步到文件系统上,则需要做更多的工作。再次,看看RDBMS的设计思想。
不要编简单的无脑代码
|
3
3
如果您谈论的是C API中的结构使用,请遵循一些简单的指导原则:
这里有一个例子——我有一个引导装载程序,它在程序映像中的固定偏移量处查找一个结构,以获取有关该映像的信息,该映像可能已被闪存到设备中。 加载器已经过修改,它支持结构中的其他项以进行一些增强。但是,可能会刷新旧的程序映像,而旧的映像使用旧的结构格式。由于从一开始就遵循了上述规则,所以较新的加载程序完全能够处理这些问题。这是最简单的部分。 如果结构被进一步修改,并且新的图像在带有旧的加载程序的设备上使用新的结构格式,那么加载程序也将能够处理它——它只是对增强功能没有任何作用。但是,由于没有字段被(或将要)删除,旧的加载程序将能够执行它设计的任何操作,并使用具有更新信息的配置结构的新映像执行操作。 如果您谈论的是一个实际的数据库,其中包含有关字段的元数据等,那么这些指导原则实际上并不适用。 |
4
2
您要寻找的是向前兼容的数据结构。有几种方法可以做到这一点。下面是低级方法。
其中“items”是描述其自身大小和类型的结构的可变长度数组
在您的代码中,您通过一些智能的强制转换迭代这些项(地址簿长度将告诉您何时到达末尾)。如果您击中了一个您不知道其ID或您不知道如何处理其类型的项目,您只需跳过该数据(从项目->大小),然后继续下一个项目。这样,如果有人在下一个版本中发明了一个新的数据字段或删除了一个,那么您的代码就能够处理它。您的代码应该能够处理有意义的转换(如果员工ID从整数变为字符串,它可能会将其作为字符串处理),但是您会发现这些情况非常罕见,通常可以用普通代码处理。 |
5
2
我以前在资源非常有限的系统中处理过这个问题,通过在PC上进行翻译作为软件升级过程的一部分。可以提取旧值,转换为新值,然后更新就地数据库吗? 对于一个简化的嵌入式数据库,我通常不直接引用任何结构,而是在任何参数周围放置一个非常轻量的API。这允许您在不影响更高级别应用程序的情况下更改API下面的物理结构。 |
6
1
最近我在用 bencoded 数据。这是BitTorrent使用的格式。很简单,您可以很容易地从视觉上检查它,因此比二进制数据更容易调试,并且压缩得很紧。我从高质量C++中借用了一些代码 libtorrent . 对于您的问题,这非常简单,只要在您读回字段时检查字段是否存在。而且,对于gzip压缩文件,它的操作非常简单:
要读回:
我以前使用过Sun的xdr格式,但现在我更喜欢这种格式。另外,使用其他语言(如Perl、Python等)也更容易阅读。 |
7
1
在结构中嵌入一个版本号,或者像win32那样,使用一个大小参数。
大约10年前,我为一个电脑游戏保存游戏系统写了一个类似的系统。实际上,我将类数据存储在一个单独的类描述文件中,如果发现版本号不匹配,那么我可以运行类描述文件,找到类,然后根据描述升级二进制类。这显然需要在新的类成员条目中填充默认值。它工作得非常好,可以用来自动生成.h和.cpp文件。 |
8
1
我同意S.Lott的观点,最好的解决方案是将你所要做的事情的物理层和逻辑层分开。实际上,您正在将接口和实现组合到一个对象/结构中,这样做时,您就错过了一些抽象的能力。 但是如果你 必须 为此,请使用单个结构,您可以做一些事情来帮助简化工作。 1) 实际上需要某种版本号字段。如果你的结构正在改变,你需要一个简单的方法来观察它并知道如何解释它。沿着这些相同的行,有时将结构的总长度存储在某个结构字段中是很有用的。 2) 如果您想保持向后兼容性,您需要记住,代码将在内部引用结构字段,作为与结构的基地址(从结构的“前”开始)的偏移量。如果要避免破坏旧代码,请确保将所有新字段添加到 后面 并保持所有现有字段不变(即使不使用它们)。这样,旧代码将能够访问结构(但最终会忽略额外的数据),新代码将可以访问所有数据。
3)
因为你的结构可能会改变尺寸,不要依赖
4) 不要删除或收缩字段;如果不需要,请将其留空。不要更改现有字段的大小;如果需要更多空间,请创建一个新字段作为旧字段的“长版本”。这可能会导致重复数据问题,因此请确保对结构进行大量考虑,并尝试规划字段,以便它们足够大,能够适应增长。 5) 不要在结构中存储字符串,除非您知道将字符串限制为某个固定长度是安全的。相反,只存储一个指针或数组索引,并创建一个字符串存储对象来保存可变长度的字符串数据。这也有助于防止字符串缓冲区溢出覆盖结构的其余数据。 我研究过的几个嵌入式项目使用这种方法来修改结构,而不破坏向后/向前的兼容性。它是有效的,但它远不是最有效的方法。不久之后,您将浪费空间,浪费了过时/废弃的结构字段、重复数据、零碎存储的数据(这里的第一个字、那边的第二个字)等。如果您被迫在现有框架内工作,那么这可能对您有用。但是,使用接口抽象出物理数据表示将更强大/更灵活,并且不那么令人沮丧(如果您有使用这种技术的设计自由)。 |
9
0
你可能想看看 Boost Serialization 图书馆处理这个问题。 |
Community wiki · C中有哪些耗时的操作? 1 年前 |
Community wiki · 将所有处理器电源都投入到任务中 1 年前 |
Community wiki · C++为C添加了什么?[已关闭] 1 年前 |
Community wiki · 打印1到1000,不带循环或条件 1 年前 |