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

时间:2019-02-20 12:19:46

标签: c++ design-patterns shared-libraries name-resolution

我怀疑是否可以再次使用源文件lib1.so构建common.cpp和使用同一源文件lib2.so构建common.cpp。现在,我想使用这两个库构建应用程序APP

我的问题是

  1. 有可能还是会给我错误?
  2. 如果它将成功构建,那么如何解决命名问题? F.e.假设foocommon.cpp的班级。 foo_v1是lib1.so中foo的对象,而foo_v2是lib2.so中foo的对象。现在,在APP爆发期间会发生什么?还可以在APP应用程序中创建foo对象吗?

1 个答案:

答案 0 :(得分:1)

自然会建议您考虑构建共享的通用功能 lib1.solib2.so放入不同的共享库libcommon.so中。

但是,如果您仍然想静态链接常用功能 完全相同 1 放入lib1.solib2.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.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

使用libfoo.sofoo.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的定义。它链接了 all common::...中的libfoo.so个符号定义。

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

因此,没有多个定义错误,并且对于使用common::...符号的“副本”或“版本”没有不确定性 通过该程序:它仅使用libfoo.so中的定义。

为什么?仅仅是因为libfoo.so是链接中提供定义的 first 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为他们提供了 first 。 仍然没有不确定性,并且对程序没有影响,因为{{1} 和libfoo.so链接了来自同一对象文件libbar.so的{​​{1}}定义。

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

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


[1]当然,如果您用不同的预处理器,编译器或链接选项编译和链接obj1.oobj2.o,则可以在某种程度上破坏“通用”功能。