2

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.

  • 3
    First, *function-templates* are implicitly `inline`. Secondly, if there is no [ODR use](https://stackoverflow.com/a/19631208/1621391) of that *function-template* in any of your libraries, the compiler will not emit any new code despite your changes. Every translation unit will have a separate copy of the instantiation of your *function-template*. Hence, when you don't recompile it, the old one will still be in use. Now, having two separate and *different* copies violates ODR, but C++ doesn't require a compiler to diagnose it – WhiZTiM Sep 19 '17 at 12:53

2 Answers2

2

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.

WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • Thank you. --- Just to make sure that I completely understand you: _libMyLibrary.so_ does instantiate its own function-templates for tmp. _exe_ also instantiates the function-template with the same typename `T` as _libMyLibrary.so_ does. --- So what you are saying for this case is that there will then be two definitions of `className` for that specific `T`. Then, since _exe_ is executed, its definition appears first and hence its definition is used. --- However, calls made by _libOtherLibrary.so_ use the new definition. Why's that? Because className is in _libOtherLibrary.so_? – Kevin van As Sep 19 '17 at 13:40
  • @KevinvanAs The consquences of ODR violations are unspecified. In fact, ODR violations will invoke the dreaded [Undefined Behavior](https://en.wikipedia.org/wiki/Undefined_behavior). The last paragraph of the linked C++ document says *"If the definitions of D do not satisfy these requirements, then the behavior is undefined."*. – WhiZTiM Sep 19 '17 at 14:24
  • @KevinvanAs, To answer your question, UB isn't something we can explain, however, the most likely event was that all the definitions are inlined in the translation unit, and they make reference to their own copy, (Though unlikely) The linker can assume you didn't violate ODR, and mess things up in an attempt to merge the definitions. – WhiZTiM Sep 19 '17 at 14:25
0

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.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547