0

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

我的问题是

  1. 有可能还是会给我错误?
  2. 如果它将成功构建,那么如何解决命名问题?Fe 可以说foo是课堂common.cppfoo_v1是 lib1.so 中 foo 的foo_v2对象,是 lib2.so 中 foo 的对象。现在在bulid期间APP会发生什么?也可以在APP应用程序中创建 foo 的对象吗?
4

1 回答 1

1

自然有人会建议您考虑将由共享的通用功能构建lib1.solib2.so一个不同的共享库中,libcommon.so.

但是,如果您仍然想将通用功能1静态链接 到两者lib1.solib2.so中,则可以将这两个共享库与您的程序链接。链接器对此没有任何问题。这是一个插图:

常见的.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;
};

常见的.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__);
}

酒吧.h

#ifndef BAR_H
#define BAR_H
#include "common.h"

struct bar
{
    void i_am() const;
private:
    common _c;
};

#endif

酒吧.cpp

#include "bar.h"

void bar::i_am() const
{
    _c.print2(__PRETTY_FUNCTION__);
}

现在我们将创建两个共享库,libfoo.solibbar.so. 我们需要的源文件是foo.cpp,bar.cppcommon.cpp. 首先将它们全部编译为PIC(位置无关代码 目标文件:

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

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

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

现在libfoo.so使用foo.oand链接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

现在我们将创建一个与这些库链接的程序:

主文件

#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::countfrom的定义./libfoo.so。同样的定义common::print1。同样的定义common::print2。它链接了所有common::...符号定义libfoo.so

common::print1它告诉我们对in的引用main.o被解析为 in 的定义libfoo.socommon::count同样,对in的引用libbar.so。同样,对common::print1common::print2in的引用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.solibbar.socommon::...common.o

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

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


[1] 当然,如果您使用不同的预处理器、编译器或链接选项进行编译和链接,您可以任意破坏“通用”功能lib1lib2

于 2019-02-20T18:53:36.077 回答