【问题标题】:C++ Inheritance and dynamic librariesC++ 继承和动态库
【发布时间】:2017-04-13 16:35:11
【问题描述】:

想法如下。我有一个库版本 1,其类如下所示:

class MY_EXPORT MyClass
{
public:
    virtual void myMethod(int p1);
}

在版本 2 中,该类被修改为:

class MY_EXPORT MyClass
{
public:
    virtual void myMethod(int p1);
    virtual void myMethod2(int p1, int p2);
}

//implementation of myMethod2 in cpp file
void MyClass::myMethod2(int p1, int p2)
{
    myMethod(p1);
    //...
}

现在想象一个用户编译了版本 1 的库,并通过覆盖 myMethod 扩展了 MyClass。现在他将库更新到版本 2,无需重新编译。让我们进一步假设动态链接器仍然成功地找到并加载了库。

问题是,如果我在库中的某个地方调用方法instance->myMethod2(1, 2); som,它会起作用,还是应用程序会崩溃?在这两种情况下,类都没有成员,因此大小相同。

【问题讨论】:

  • 由于继承的类不知道改变的接口,它的虚拟表中不会有关于它的信息。这将导致未定义的行为。简而言之:如果接口发生变化,需要重新编译依赖于这些接口的应用程序。

标签: c++ shared-libraries virtual-functions


【解决方案1】:

我认为没有必要猜测该应用程序是否会崩溃,行为未定义。必须重新编译应用程序,因为库中的 ABI 发生了变化。

当库调用instance->myMethod2(1, 2); 时,它必须通过在应用程序代码中创建的虚拟表,假设只有一个虚拟方法:myMethod。从那时起,您将获得未定义的行为。简而言之,当库 ABI 发生变化时,您必须重新编译您的应用程序。

【讨论】:

  • 崩溃实际上是首选,因为此时没有歧义,你知道你有未定义的行为。
  • @MarkRansom 是的,它已被提供,但在某些情况下它可能会默默地工作并做错事,甚至完全符合预期。
【解决方案2】:

KDE C++ ABI guidelines明确禁止此类更改。派生类的虚拟表将不包含新方法的地址,因此对派生类对象的这些方法的虚拟调用将崩溃。

【讨论】:

    【解决方案3】:

    通过更改类的定义而不重新编译,您违反了One Definition Rule。未重新编译的用户正在使用旧定义,而您的库正在使用新定义。这会导致未定义的行为。

    要了解这可能如何体现,请考虑使用 VTable 调度函数调用的虚拟函数的典型实现。库用户已经派生了一个类,而这个派生类在 VTable 中只有一个函数。如果一个指向这个类的指针或引用被传递到库中,并且库尝试调用第二个函数,它将尝试访问一个不存在的 VTable 条目。这几乎总是会导致崩溃,尽管在涉及未定义的行为时没有任何保证。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-21
      • 2013-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多