C++ - 如果两个库使用相同的源代码进行构建会发生什么

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

我怀疑如果我再次使用源文件 common.cpp 构建 lib1.so 并使用相同的源文件 common.cpp 构建 lib2.so 是否可行。现在我想使用这两个库构建我的应用程序 APP

我的问题是

  1. 这可能吗?还是会报错?
  2. 如果构建成功,那么命名是如何解决的? F.e。假设 foocommon.cpp 中是 class。 foo_v1 是 lib1.so 中 foo 的对象,foo_v2 是 lib2.so 中 foo 的对象。现在在 APP 期间会发生什么?也可以在 APP 应用程序中创建 foo 对象吗?

自然有人会建议您考虑构建共享的通用功能 通过 lib1.solib2.so 进入不同的共享库,libcommon.so.

但是如果你想静态地link通用功能 同样1 进入 lib1.solib2.so,你可以 link 这两个共享库 你的程序。 linker 对此没有问题。这是一个 插图:

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.solibbar.so。这 我们需要的源文件是 foo.cppbar.cppcommon.cpp。第一的 将它们全部编译为 PIC (Position Independent Code 目标文件:

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

这是我们刚刚制作的目标文件:

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

现在 link libfoo.so 使用 foo.ocommon.o:

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

然后 link 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

现在我们将使用这些库link编写一个程序:

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)

这很好。您可能担心静态 class 变量的 两个副本 common::count 将最终出现在程序中 - 一个来自 libfoo.so,另一个来自 libbar.sofoo 会递增一个副本,而 bar 会递增另一个。但那并没有发生。

linker 是如何解析 common::... 符号的?好吧,我们需要找到他们被破坏的形式, 正如 link 人看到的那样:

$ 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 的 linkage,这次请 linker 告诉我们 在其中定义或引用了这些 common::... 符号的输入文件。这个诊断 link年龄有点大,我就\-拆分一下:

$ 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

所以 linker 告诉我们它 link 从 ./libfoo.so 编辑了 common::count 的定义。同样的 common::print1 的定义。同样定义 common::print2。它 linked all common::... 来自 libfoo.so.

的符号定义

它告诉我们 main.o 中对 common::print1 的引用已解析为 libfoo.so 中的定义。同样地 libbar.so 中对 common::count 的引用。同样,对 common::print1common::print2libbar.so 中。 All程序中的common::...符号引用被解析为 libfoo.so.

提供的定义

所以没有出现多重定义错误,也没有不确定common::...符号中的"copies"或"versions"用过的 通过程序:它只使用 libfoo.so.

中的定义

为什么?仅仅因为 libfoo.so 是 link 时代第一个 提供定义的 图书馆 对于 common::... 符号。如果我们将linkprog-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 提供了他们 first。 仍然没有不确定性,对程序没有影响,因为libfoo.solibbar.so link 从同一个对象文件 common.o.

编辑了 common::... 定义

linker 不会尝试查找符号的多个定义。一旦找到一个 输入目标文件或共享库中符号 S 的定义,它将引用绑定到 S 到它找到的定义,并通过解析 S 完成。它确实 不在乎它以后找到的共享库是否可以提供 S 的另一个定义,相同或不同, 即使后来的共享库解析符号 other 而不是 S.

导致多重定义错误的唯一方法是强制 linker 到静态link多个定义,即强制它物理合并到输出二进制文件中 两个 目标文件 obj1.oobj2.o 都包含定义 S。 如果这样做,竞争的静态定义将具有完全相同的状态,并且仅 该程序可以使用一个定义,因此 linker 必须让您失望。但它不需要注意到一个 共享库 提供的 S 的动态符号定义如果它已经解析了 S,但它没有这样做。


[1] 当然,如果您使用不同的预处理器、编译器或 linkage 选项编译 link lib1lib2,您可以破坏 "common" 功能任意程度。