自然有人会建议您考虑将由共享的通用功能构建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.o
and链接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::count
from的定义./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::print2
in的引用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