C++ - Что произойдет, если две библиотеки используют один и тот же исходный код для сборки

Я сомневаюсь, что это возможно, если я построил 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 приложение?

2 ответа

Решение

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

Или это даст мне ошибку?

Вы, вероятно, получите ссылки на переопределение символов / множественные ошибки при связывании.

Является ли это возможным

Вполне возможно. Даже если вы используете тот же файл common.cpp, вы не обязательно получите те же двоичные файлы. Вы можете использовать флаги препроцессора и компиляции, чтобы настроить создаваемый двоичный файл (например, использовать разные пространства имен или настроить именование) и, таким образом, избежать ошибок переопределения символов.

Если он будет успешно построен, то как будет решаться вопрос с именами?

Связывание не удастся, если произойдет несколько определений символов. Компоновщик не сможет определить, какая версия нужна.

Другие вопросы по тегам