C++: What to recompile when an inline function in a dynamic library changes?

837 views Asked by At

I have two dynamic libraries and one executable:

  • libOtherLibrary.so
    • This is an existing open-source library written by someone else.
  • libMyLibrary.so
    • This is my own library that depends on libOtherLibrary.so.
  • exe
    • This is my own executable that depends on both libraries.

As a test to see when a specific function is called, I added a print statement to an inline function of libOtherLibrary.so (code details shouldn't matter):

template<class T>
inline void className<T>::clear() const
{
    Info << "Hello World!" << endl; // My message!
    if (isTmp() && ptr_)
    {
        if (ptr_->unique())
        {
            delete ptr_;
            ptr_ = 0;
        }
        else
        {
            ptr_->operator--();
            ptr_ = 0;
        }
    }
}

I then recompiled libOtherLibrary.so, followed by recompiling libMyLibrary.so. Finally I relinked (so no recompilation) exe.

The result was that any call to className<T>::clear() initiated in libMyLibrary.so used the old implementation of this inline method, whereas any call to className<T>::clear() initiated by libOtherLibrary.so used the new implementation.

When I then decided to also recompile exe (followed by linking it), the result was that the new implementation was always used.


My question is: Can someone explain to me why exe required recompilation, rather than relinking only?

That is, the inlining of the function className<T>::clear() of libOtherLibrary.so should occur during the compilation stage of libMyLibrary.so, doesn't it? After all, it is a function contained in libMyLibrary.so whom calls className<T>::clear(). Then I'd expect that linking exe is sufficient, as exe does not call this particular inline function. The linker alone will take care of any changed ABI compatability.

2

There are 2 answers

3
WhiZTiM On BEST ANSWER

My question is: Can someone explain to me why exe required recompilation, rather than relinking only?

Because, for your specific use-case, without it, you will inccur the wrath of ODR violation.


The result was that any call to className<T>::clear() initiated in libMyLibrary.so used the old implementation of this inline method, whereas any call to className<T>::clear() initiated by libOtherLibrary.so used the new implementation.

When you have a function-template say:

template<class T>
inline void className<T>::clear(){
    ....
}

And it is ODR used in multiple translation units (.cpp file). It's instantiation will be defined in each one of such translation unit because function-templates are implicitly inline.

The rules for such multiple definition are stated here basic.def.odr/6. And one of the listed requirements states that "each definition of D shall consist of the same sequence of tokens;".

Modifying that function template and recompiling some translation units making ODR use of it, and linking your program, without recompiling all the translation units making ODR-use of it violates the holy One Definition Rule of C++.

Compiler toolchains are not required to diagnose it.

0
Basile Starynkevitch On

Another way of saying it:

Assuming the compiler does inlining (which is unrelated to inline keyword, e.g. GCC would try hard to inline if you compile and link with g++ -flto -O2 even functions which are not marked with inline), the code should be recompiled as soon as the definition of the inlined function changes. In most good C++ programs, that definition occurs in some header files (containing explicitly inline functions).

So you should recompile when header files change. Good build automation tools (e.g. make combined with g++ -MD) handle that.