自然有人会建议您考虑将由共享的通用功能构建lib1.so到lib2.so一个不同的共享库中,libcommon.so.
但是,如果您仍然想将通用功能1静态链接
到两者lib1.so和lib2.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.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.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.so。common::count同样,对in的引用libbar.so。同样,对common::print1和
common::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] 当然,如果您使用不同的预处理器、编译器或链接选项进行编译和链接,您可以任意破坏“通用”功能
lib1。
lib2