C++ - What would happen if two library uses same source code for build

1.3k views Asked by At

I have doubt is it possible if I built lib1.so using source file common.cppand lib2.so using same source file common.cpp again. Now I want to build my application APP using this two library ,

My question are

  1. is it possible Or it will give me error ?
  2. If it will successfully built then how naming gets resolved? F.e. let say foo is class in common.cpp. foo_v1 is object of foo in lib1.so and foo_v2 is object of foo in lib2.so. Now during bulid of APP what would happen? Also is it possible to create object of foo in APP application ?
1

There are 1 answers

2
Mike Kinghan On BEST ANSWER

Naturally one would suggest you consider building the common functionality shared by lib1.so and lib2.so into a distinct shared library, libcommon.so.

But if you want nevertheless to statically link the common functionality identically1 into both lib1.so and lib2.so, you can link these two shared libraries with your program. The linker will have no problem with that. Here is an illustration:

common.h

#ifndef COMMON_H
#define COMMON_H
#include <string>

struct common
{
    void print1(std::string const & s) const;
    void print2(std::string const & s) const;
    static unsigned count;
};

common.cpp

#include <iostream>
#include "common.h"

unsigned common::count = 0;

void common::print1(std::string const & s) const
{
    std::cout << s << ". (count = " << count++ << ")" << std::endl;
}

void common::print2(std::string const & s) const
{
    std::cout << s << ". (count = " << count++ << ")" << std::endl;
}

foo.h

#ifndef FOO_H
#define FOO_H
#include "common.h"

struct foo
{
    void i_am() const;
private:
    common _c;
};

#endif

foo.cpp

#include "foo.h"

void foo::i_am() const
{
    _c.print1(__PRETTY_FUNCTION__);
}

bar.h

#ifndef BAR_H
#define BAR_H
#include "common.h"

struct bar
{
    void i_am() const;
private:
    common _c;
};

#endif

bar.cpp

#include "bar.h"

void bar::i_am() const
{
    _c.print2(__PRETTY_FUNCTION__);
}

Now we'll make two shared libraries, libfoo.so and libbar.so. The source files we need are foo.cpp, bar.cpp and common.cpp. First compile them all to PIC (Position Independent Code object files:

$ g++ -Wall -Wextra -fPIC -c foo.cpp bar.cpp common.cpp

And here are the object files we just made:

$ ls *.o
bar.o  common.o  foo.o

Now link libfoo.so using foo.o and common.o:

$ g++ -shared -o libfoo.so foo.o common.o

Then link libbar.so using bar.o and (again) common.o

$ g++ -shared -o libbar.so bar.o common.o

We can see that common::... symbols are defined and exported by libfoo.so:

$ nm -DC libfoo.so | grep common
0000000000202094 B common::count
0000000000000e7e T common::print1(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const
0000000000000efa T common::print2(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const

(T means defined in the code section, B means defined in the uinitialized data section). And exactly the same is true about libbar.so

$ nm -DC libbar.so | grep common
0000000000202094 B common::count
0000000000000e7e T common::print1(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const
0000000000000efa T common::print2(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const

Now we'll make a program linked with these libraries:

main.cpp

#include "foo.h"
#include "bar.h"

int main()
{
    foo f;
    bar b;
    common c;

    f.i_am();
    b.i_am();
    c.print1(__PRETTY_FUNCTION__);
    return 0;
}

It calls foo; it calls bar, and it calls common::print1.

$ g++ -Wall -Wextra -c main.cpp
$ g++ -o prog main.o -L. -lfoo -lbar -Wl,-rpath=$PWD

It runs like:

$ ./prog
void foo::i_am() const. (count = 0)
void bar::i_am() const. (count = 1)
int main(). (count = 2)

Which is just fine. You might perhaps have worried that two copies of the static class variable common::count would end up in the program - one from libfoo.so and another from libbar.so, and that foo would increment one copy and bar would increment the other. But that didn't happen.

How did the linker resolve the common::... symbols? Well to see that we need to find their mangled forms, as the linker sees them:

$ nm common.o | grep common
0000000000000140 t _GLOBAL__sub_I_common.cpp
0000000000000000 B _ZN6common5countE
0000000000000000 T _ZNK6common6print1ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
000000000000007c T _ZNK6common6print2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE

There they all are and we can tell which one is which with c++filt:

$ c++filt _ZN6common5countE
common::count

$ c++filt _ZNK6common6print1ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
common::print1(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const

$ c++filt _ZNK6common6print2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
common::print2(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const

Now we can re-do the linkage of prog, this time asking the linker to tell us the names of the input files in which these common::... symbols were defined or referenced. This diagnostic linkage is a bit of a mouthful, so I'll \-split it:

$ g++ -o prog main.o -L. -lfoo -lbar -Wl,-rpath=$PWD \
    -Wl,-trace-symbol=_ZN6common5countE \
    -Wl,-trace-symbol=_ZNK6common6print1ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE \
    -Wl,-trace-symbol=_ZNK6common6print2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE        
main.o: reference to _ZNK6common6print1ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
./libfoo.so: definition of _ZN6common5countE
./libfoo.so: definition of _ZNK6common6print2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
./libfoo.so: definition of _ZNK6common6print1ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
./libbar.so: reference to _ZN6common5countE
./libbar.so: reference to _ZNK6common6print2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
./libbar.so: reference to _ZNK6common6print1ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE

So the linker tells us that it linked the definition of common::count from ./libfoo.so. Likewise the definition of common::print1. Likewise the definition of common::print2. It linked all the common::... symbol definitions from libfoo.so.

It tells us that the reference(s) to common::print1 in main.o was resolved to the definition in libfoo.so. Likewise the reference(s) to common::count in libbar.so. Likewise the reference(s) to common::print1 and common::print2 in libbar.so. All the common::... symbol references in the program were resolved to the definitions provided by libfoo.so.

So there were no multiple definition errors, and there is no uncertainty about which "copies" or "versions" of the common::... symbols are used by the program: it just uses the definitions from libfoo.so.

Why? Simply because libfoo.so was the first library in the linkage that provided definitions for the common::...symbols. If we relink prog with the order of -lfoo and -lbar reversed:

$ g++ -o prog main.o -L. -lbar -lfoo -Wl,-rpath=$PWD \
    -Wl,-trace-symbol=_ZN6common5countE \
    -Wl,-trace-symbol=_ZNK6common6print1ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE \
    -Wl,-trace-symbol=_ZNK6common6print2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE        
main.o: reference to _ZNK6common6print1ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
./libbar.so: definition of _ZN6common5countE
./libbar.so: definition of _ZNK6common6print2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
./libbar.so: definition of _ZNK6common6print1ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
./libfoo.so: reference to _ZN6common5countE
./libfoo.so: reference to _ZNK6common6print2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
./libfoo.so: reference to _ZNK6common6print1ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE

then we get exactly the opposite answers. All the common::... symbol references in the program are now resolved to the definitions provided by libbar.so. Because libbar.so provided them first. There is still no uncertainty, and it makes no difference to the program, because both libfoo.so and libbar.so linked the common::... definitions from the same object file, common.o.

The linker does not try to find multiple definitions of symbols. Once it has found a definition of a symbol S, in an input object file or shared library, it binds references to S to the definition it has found and is done with resolving S. It does not care if a shared library it finds later can provide another definition of S, the same or different, even if that later shared library resolves symbols other than S.

The only way in which you can cause a multiple definition error is by compelling the linker to statically link multiple definitions, i.e. compel it to physically merge into the output binary two object files obj1.o and obj2.o both of which contain a definition S. If you do that, the competing static definitions have exactly the same status, and only one definition can be used by the program, so the linker has to fail you. But it does not need to take any notice of a dynamic symbol definition of S provided by a shared library if it has already resolved S, and it does not do so.


[1] Of course, if you compile and link lib1 and lib2 with different preprocessor, compiler or linkage options, you can sabotage the "common" functionality to an arbitary extent.