2

我有一个关于对象的不同版本、它们的大小和分配的问题。该平台是 Solaris 8(及更高版本)。

假设我们有程序 A、B 和 C,它们都链接到共享库 D。在库 D 中定义了一些类,我们称其为“classD”,并假设大小为 100 字节。现在,我们想为下一个版本的程序 A 向 classD 添加一些成员,而不影响现有的二进制文件 B 或 C。新的大小将是 120 字节。我们希望程序 A 使用 D 类的新定义(120 字节),而程序 B 和 C 继续使用 D 类的旧定义(100 字节)。A、B 和 C 都使用运算符“new”来创建 D 的实例。

问题是,运算符“new”什么时候知道要分配的内存量?编译时还是运行时?我害怕的一件事是,程序 B 和 C 期望 classD 分配 100 个字节,而新的共享库 D 需要 120 个字节用于 classD,如果我将它们与新库 D。换句话说,新类 D 需要的额外 20 个字节的区域可能会被程序 B 和 C 分配给其他一些变量。这个假设是否正确?

谢谢你的帮助。

4

6 回答 6

6

更改类的大小是二进制不兼容的。这意味着如果您在classD不重新编译使用它的代码的情况下更改大小,您将获得未定义的行为(很可能会崩溃)。

解决此限制的一个常见技巧是进行设计classD,以便可以以二进制兼容的方式安全地扩展它,例如使用Pimpl idiom。

无论如何,如果您希望不同的程序使用您的课程的不同版本,我认为您别无选择,只能发布共享库的多个版本并将这些程序链接到适当的版本。

于 2009-07-06T17:08:27.683 回答
3

编译时,您不应更改其客户端下的共享对象大小。

有一个简单的解决方法:

class foo
{
public:
  // make sure this is not inlined
  static foo* Create()
  {
     return new foo();
  }
}

// at the client
foo* f = foo::Create();
于 2009-07-06T16:48:31.173 回答
2

您是正确的,内存大小是在编译时定义的,应用程序 B/C 将面临严重的内存损坏问题。

没有办法在语言级别明确地处理这个问题。您需要与操作系统合作以将适当的共享库提供给应用程序。

您需要对库进行版本控制。

由于没有明确的方法可以使用构建工具执行此操作,因此您需要使用文件名来执行此操作。如果您查看大多数产品,这大概就是它们的工作原理。

在 lib 目录中:

libD.1.00.so
libD.1.so     ->  libD.1.00.so    // Symbolic link
libD.so       ->  libD.1.so      // Symbolic link

现在在编译时指定 -lD 并链接到 libD.1.00.so,因为它遵循符号链接。在运行时,它知道使用这个版本,因为这是它编译时所针对的版本。

所以你现在将 lib D 更新到 2.0 版

在 lib 目录中:

libD.1.00.so
libD.2.00.so
libD.1.so     ->  libD.1.00.so    // Symbolic link
libD.2.so     ->  libD.2.00.so    // Symbolic link
libD.so       ->  libD.2.so       // Symbolic link

现在,当您使用 -libD 构建时,它会链接到版本 2。因此,您重新构建 A,它将从现在开始使用 lib 的版本 2;而 B 和 C 仍将使用版本 1。如果您重建 B 或 C,它将使用新版本的库,除非您在构建 -libD.1 时明确使用旧版本的库

一些链接器不知道如何很好地遵循符号链接,因此有一些链接器命令可以提供帮助。gcc 使用“-install_name”标志你的链接器可能有一个稍微不同的命名标志。

作为运行时检查,将版本信息放入共享对象(全局变量/函数调用等)通常是一个好主意。因此,在运行时,您可以检索共享库版本信息并检查您的应用程序是否兼容。如果不是,您应该退出并显示相应的错误消息。

另请注意:如果将 D 的对象序列化到文件中。您知道需要确保维护有关 D 的版本信息。Libd.2 可能知道如何读取版本 1 D 对象(通过一些显式的工作),但反之则不成立。

于 2009-07-06T17:15:09.323 回答
1

内存分配是在编译时计算出来的。在 D 中更改类的大小将触发重新编译。

如果适用,请考虑从相关类公开派生以扩展它。或者,将其组合在另一个对象中。

于 2009-07-06T16:45:42.970 回答
1

要分配的内存量是在编译时确定的

new Object();

但它可以是动态参数,例如

new unsigned char[variable];

我真的建议你通过一些中间件来实现你想要的。C++ 在二进制接口方面没有任何保证。

你看过protobuf吗?

于 2009-07-06T16:47:35.167 回答
0

除了提到的“ad hoc”技术之外,您还可以通过说您的新类 A 实际上是“旧”类 A 的子类来对系统的兼容性进行建模。这样,您的旧代码继续工作,但所有代码需要扩展功能的需要修改。

这种设计原则在 COM 世界中是清晰可见的,特别是接口永远不会随着版本而改变,只会通过继承来扩展。其次,他们只通过CreateInstance方法构造类,将分配问题转移到包含该类的库中。

于 2009-07-06T18:06:38.673 回答