Естественно, кто-то может предложить вам объединить общие функции, совместно используемые lib1.so
и lib2.so
, в отдельную общую библиотеку, libcommon.so
.
Но если вы все же хотите статически связать общие функции одинаково 1 в lib1.so
и lib2.so
, вы можете связать эти две общие библиотеки с вашей программой.У линкера с этим проблем не будет.Вот иллюстрация:
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__);
}
Теперь мы сделаем две общие библиотеки, libfoo.so
и libbar.so
.Нам нужны исходные файлы foo.cpp
, bar.cpp
и common.cpp
.Сначала скомпилируйте их в PIC (позиционный независимый код объектные файлы:
$ g++ -Wall -Wextra -fPIC -c foo.cpp bar.cpp common.cpp
И вот объектные файлы, которые мы только что создали:
$ ls *.o
bar.o common.o foo.o
Теперь ссылка libfoo.so
с использованием foo.o
и common.o
:
$ g++ -shared -o libfoo.so foo.o common.o
Затем ссылка libbar.so
с использованием bar.o
и (снова) common.o
$ g++ -shared -o libbar.so bar.o common.o
Мы можем видеть, что common::...
символов определены и экспортированы как 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
означает , определенное в разделе кода , B
означает определено в разделе неинициализированных данных ). То же самое верно и для 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
Теперь мы создадим программу, связанную с этими библиотеками:
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;
}
Он вызывает foo
, он вызывает bar
и common::print1
.
$ g++ -Wall -Wextra -c main.cpp
$ g++ -o prog main.o -L. -lfoo -lbar -Wl,-rpath=$PWD
Он работает так:
$ ./prog
void foo::i_am() const. (count = 0)
void bar::i_am() const. (count = 1)
int main(). (count = 2)
Это нормально. Возможно, вы беспокоитесь, что две копии статической переменной класса common::count
окажутся в программе - одна из libfoo.so
, а другая изlibbar.so
, и что foo
будет включатьпометите одну копию, а bar
увеличит другую.Но этого не произошло.
Как компоновщик разрешил символы common::...
?Хорошо видеть, что нам нужно найти их искаженные формы, как видит их компоновщик:
$ 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
Там все они есть, и мы можем сказать, какой из них с 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
Теперь мы можем заново выполнить связывание prog
, на этот раз попросив компоновщика сообщить нам имена входных файлов, в которых были определены или указаны эти символы common::...
.Эта диагностическая связь немного устарела, поэтому я ее \
разделю:
$ 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
Так что компоновщик говорит нам, что он связал определение common::count
с ./libfoo.so
.Аналогично определению common::print1
.Аналогично определению common::print2
.Он связал все определения символов common::...
с libfoo.so
.
Он говорит нам, что ссылка (и) на common::print1
в main.o
была разрешена к определению в libfoo.so
.Аналогично ссылки на common::count
в libbar.so
.Аналогично ссылки на common::print1
и common::print2
в libbar.so
. Все ссылки на символы common::...
в программе разрешены в определения, предоставленные libfoo.so
.
Так что не было ошибок множественное определение , и естьнет никакой неопределенности относительно того, какие «копии» или «версии» символов common::...
используются программой: она просто использует определения из libfoo.so
.
Почему?Просто потому, что libfoo.so
была первой библиотекой в связке, которая предоставляла определения для символов common::...
.Если мы свяжем prog
с порядком -lfoo
и -lbar
в обратном порядке:
$ 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
, тогда мы получим совершенно противоположные ответы. Все ссылки на символы common::...
в программе теперь разрешены в определения, предоставленные libbar.so
.Потому что libbar.so
предоставил им сначала .По-прежнему нет никакой неопределенности, и это не имеет никакого значения для программы, потому что и libfoo.so
, и libbar.so
связали определения common::...
из одного и того же объектного файла, common.o
.
Компоновщик не попробуйте , чтобы найти несколько определений символов.Найдя определение символа S во входном объектном файле или в общей библиотеке, он связывает ссылки с S к определению, которое он нашел, и делается с разрешением S .Не имеет значения, может ли обнаруженная позднее совместно используемая библиотека предоставить другое определение S , такое же или другое, даже если эта более поздняя совместно используемая библиотека разрешает символы , отличающиеся , чем S .
Единственный способ, которым вы можете вызвать ошибку множественного определения, - это заставить компоновщик статически связать множественные определения, то есть заставить его физически объединиться в выходной двоичный файл два объектные файлы obj1.o
и obj2.o
, каждый из которых содержит определение S .Если вы сделаете это, конкурирующие статические определения имеют точно такой же статус, и программа может использовать только одно определение, поэтому компоновщик должен вас подвести.Но не нужно обращать внимания на динамическое определение символа S , предоставляемое общей библиотекой , если оно уже разрешило S , и не делает этого.
[1] Конечно, если вы компилируете и связываете
lib1
и
lib2
с различными параметрами препроцессора, компилятора или связывания, вы можете саботировать «общие» функции в произвольной степени.