Pure virtual functions and binary compatibility

為{幸葍}努か 提交于 2019-12-04 05:54:01

ABI basically depends on the size and shape of the object, including the vtable. Adding a virtual function will definitely change the vtable, and how it changes depends on the compiler.

Something else to consider in this case is that you're not just proposing an ABI breaking change, but an API breaking one that is very difficult to detect at compile time. If these were not virtual functions and ABI compatibility wasn't an issue, after your change, something like:

void f(Interface * i) {
  i->Foo(1)
}

will quietly end up calling your new function, but only if that code is recompiled, which can make debugging very difficult.

The simple answer is: no. Anytime you change the class definition at all, you potentially lose binary compatibility. Adding a non-virtual function or static members is usually safe in practice, although still formally undefined behavior, but that's about it. Anything else will probably break binary compatibility.

You are trying to describe the popular "Make classes non-derivable" technique for preserving binary compatibility which is used, for example, in the Symbian C++ APIs (look for NewL factory method):

  1. Provide a factory function;
  2. Declare the C++ constructor private (and non-exported non-inline, and the class should not have friend classes or functions), this makes the class non-derivable and then you can:

    • Add virtual functions at end of the class declaration,
    • Add data members and change the size of the class.

This technique works only for GCC compiler because it saves the source order of virtual functions at the binary level.

Explanation

Virtual functions are invoked by the offset in the v-table of an object, not by the mangled name. If you can get the object pointer only by calling a static factory method and preserved the offset of all virtual functions (by saving the source order, adding new methods at end) then this will be backward binary compatible.

The compatibility will be broken if your class has a public constructor (inline or non-inline):

  • inline: applications will copy an old v-table and old memory layout of the class which will be different from the ones used in the new library; if you call any exported method or pass an object as the argument to such method then this may cause a memory corruption of segmentation fault;

  • non-inline: the situation is better, because you can change v-table by adding new virtual methods to the end of leaf class declaration, because the linker will relocate the v-table layout of derived classes at the client side if you'll load the new library version; but you still cannot change the size of the class (i.e. adding new fields), because the size is hard-coded at the compile time and calling a new-version constructor may break the memory of neighboring objects on the client stack or heap.

Tools

Try to use the abi-compliance-checker tool to check backward binary compatibility of your class library versions on Linux.

It was very amazing for me when I was in similar situation and I found, that MSVC reverses the order of overloaded functions. According to your example, MSVC will construct the v_table (in binary) like this:

virtual void Foo( uint32_t arg ) = 0;
virtual void Foo( uint16_t arg ) = 0;

If we'll widen a little your example, like this:

class Interface {
    virtual void first() = 0;
    virtual void Foo( uint16_t arg ) = 0;
    virtual void Foo( uint32_t arg ) = 0;
    virtual void Foo( std::string arg ) = 0;
    virtual void final() = 0;
}

MSVC will construct the following v_table:

    virtual void first() = 0;
    virtual void Foo( std::string arg ) = 0;
    virtual void Foo( uint32_t arg ) = 0;
    virtual void Foo( uint16_t arg ) = 0;
    virtual void final() = 0;

Borland builder and GCC do not change the order, but

  1. They do not this in that versions, that I tested
  2. If your library compiled by GCC (for example), and app will be compiled by MSVC, it would be an epic fail

An the end... Never rely to binary compatibility. Any change of class must cause recompile of all the code, using it.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!