我怀疑是否可以再次使用源文件lib1.so
构建common.cpp
和使用同一源文件lib2.so
构建common.cpp
。现在,我想使用这两个库构建应用程序APP
,
我的问题是
foo
是common.cpp
的班级。 foo_v1
是lib1.so中foo的对象,而foo_v2
是lib2.so中foo的对象。现在,在APP
爆发期间会发生什么?还可以在APP
应用程序中创建foo对象吗?答案 0 :(得分:1)
自然会建议您考虑构建共享的通用功能
lib1.so
和lib2.so
放入不同的共享库libcommon.so
中。
但是,如果您仍然想静态链接常用功能
完全相同 1
放入lib1.so
和lib2.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.so
和libbar.so
。的
我们需要的源文件是foo.cpp
,bar.cpp
和common.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.so
和foo.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::print1
和
common::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 ,则不会这样做。
obj1.o
和obj2.o
,则可以在某种程度上破坏“通用”功能。