C ++ - Что произойдет, если две библиотеки используют один и тот же исходный код для сборки - PullRequest
0 голосов
/ 20 февраля 2019

У меня есть сомнения, возможно ли это, если я собрал lib1.so, используя исходный файл common.cpp и lib2.so, снова используя тот же исходный файл common.cpp.Теперь я хочу построить свое приложение APP, используя эти две библиотеки,

Мой вопрос:

  1. Возможно ли это или это даст мне ошибку?
  2. Если он будет успешно построен, то как будет разрешаться именование?Например, скажем, foo - это класс common.cpp.foo_v1 является объектом foo в lib1.so, а foo_v2 является объектом foo в lib2.so.Теперь во время булида APP что произойдет?Также возможно ли создать объект foo в приложении APP?

1 Ответ

0 голосов
/ 20 февраля 2019

Естественно, кто-то может предложить вам объединить общие функции, совместно используемые 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 с различными параметрами препроцессора, компилятора или связывания, вы можете саботировать «общие» функции в произвольной степени.
...