C++ - 如果两个库使用相同的源代码进行构建会发生什么
C++ - What would happen if two library uses same source code for build
我怀疑如果我再次使用源文件 common.cpp
构建 lib1.so
并使用相同的源文件 common.cpp
构建 lib2.so
是否可行。现在我想使用这两个库构建我的应用程序 APP
,
我的问题是
- 这可能吗?还是会报错?
- 如果构建成功,那么命名是如何解决的?
F.e。假设
foo
在 common.cpp
中是 class。 foo_v1
是 lib1.so 中 foo 的对象,foo_v2
是 lib2.so 中 foo 的对象。现在在 APP
期间会发生什么?也可以在 APP
应用程序中创建 foo 对象吗?
自然有人会建议您考虑构建共享的通用功能
通过 lib1.so
和 lib2.so
进入不同的共享库,libcommon.so
.
但是如果你想静态地link通用功能
同样1
进入 lib1.so
和 lib2.so
,你可以 link 这两个共享库
你的程序。 linker 对此没有问题。这是一个
插图:
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
现在 link libfoo.so
使用 foo.o
和 common.o
:
$ g++ -shared -o libfoo.so foo.o common.o
然后 link 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
现在我们将使用这些库link编写一个程序:
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)
这很好。您可能担心静态 class 变量的 两个副本
common::count
将最终出现在程序中 - 一个来自 libfoo.so
,另一个来自 libbar.so
,
foo
会递增一个副本,而 bar
会递增另一个。但那并没有发生。
linker 是如何解析 common::...
符号的?好吧,我们需要找到他们被破坏的形式,
正如 link 人看到的那样:
$ 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
的 linkage,这次请 linker 告诉我们
在其中定义或引用了这些 common::...
符号的输入文件。这个诊断
link年龄有点大,我就\
-拆分一下:
$ 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
所以 linker 告诉我们它 link 从 ./libfoo.so
编辑了 common::count
的定义。同样的
common::print1
的定义。同样定义 common::print2
。它 linked all
common::...
来自 libfoo.so
.
的符号定义
它告诉我们 main.o
中对 common::print1
的引用已解析为 libfoo.so
中的定义。同样地
libbar.so
中对 common::count
的引用。同样,对 common::print1
和
common::print2
在 libbar.so
中。 All程序中的common::...
符号引用被解析为
libfoo.so
.
提供的定义
所以没有出现多重定义错误,也没有不确定common::...
符号中的"copies"或"versions"用过的
通过程序:它只使用 libfoo.so
.
中的定义
为什么?仅仅因为 libfoo.so
是 link 时代第一个 提供定义的 图书馆
对于 common::...
符号。如果我们将linkprog
与-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。
仍然没有不确定性,对程序没有影响,因为libfoo.so
和 libbar.so
link 从同一个对象文件 common.o
.
编辑了 common::...
定义
linker 不会尝试查找符号的多个定义。一旦找到一个
输入目标文件或共享库中符号 S 的定义,它将引用绑定到
S 到它找到的定义,并通过解析 S 完成。它确实
不在乎它以后找到的共享库是否可以提供 S 的另一个定义,相同或不同,
即使后来的共享库解析符号 other 而不是 S.
导致多重定义错误的唯一方法是强制 linker
到静态link多个定义,即强制它物理合并到输出二进制文件中
两个 目标文件 obj1.o
和 obj2.o
都包含定义 S。
如果这样做,竞争的静态定义将具有完全相同的状态,并且仅
该程序可以使用一个定义,因此 linker 必须让您失望。但它不需要注意到一个
共享库 提供的 S 的动态符号定义如果它已经解析了 S,但它没有这样做。
[1] 当然,如果您使用不同的预处理器、编译器或 linkage 选项编译 link lib1
和 lib2
,您可以破坏 "common" 功能任意程度。
我怀疑如果我再次使用源文件 common.cpp
构建 lib1.so
并使用相同的源文件 common.cpp
构建 lib2.so
是否可行。现在我想使用这两个库构建我的应用程序 APP
,
我的问题是
- 这可能吗?还是会报错?
- 如果构建成功,那么命名是如何解决的?
F.e。假设
foo
在common.cpp
中是 class。foo_v1
是 lib1.so 中 foo 的对象,foo_v2
是 lib2.so 中 foo 的对象。现在在APP
期间会发生什么?也可以在APP
应用程序中创建 foo 对象吗?
自然有人会建议您考虑构建共享的通用功能
通过 lib1.so
和 lib2.so
进入不同的共享库,libcommon.so
.
但是如果你想静态地link通用功能
同样1
进入 lib1.so
和 lib2.so
,你可以 link 这两个共享库
你的程序。 linker 对此没有问题。这是一个
插图:
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
现在 link libfoo.so
使用 foo.o
和 common.o
:
$ g++ -shared -o libfoo.so foo.o common.o
然后 link 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
现在我们将使用这些库link编写一个程序:
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)
这很好。您可能担心静态 class 变量的 两个副本
common::count
将最终出现在程序中 - 一个来自 libfoo.so
,另一个来自 libbar.so
,
foo
会递增一个副本,而 bar
会递增另一个。但那并没有发生。
linker 是如何解析 common::...
符号的?好吧,我们需要找到他们被破坏的形式,
正如 link 人看到的那样:
$ 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
的 linkage,这次请 linker 告诉我们
在其中定义或引用了这些 common::...
符号的输入文件。这个诊断
link年龄有点大,我就\
-拆分一下:
$ 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
所以 linker 告诉我们它 link 从 ./libfoo.so
编辑了 common::count
的定义。同样的
common::print1
的定义。同样定义 common::print2
。它 linked all
common::...
来自 libfoo.so
.
它告诉我们 main.o
中对 common::print1
的引用已解析为 libfoo.so
中的定义。同样地
libbar.so
中对 common::count
的引用。同样,对 common::print1
和
common::print2
在 libbar.so
中。 All程序中的common::...
符号引用被解析为
libfoo.so
.
所以没有出现多重定义错误,也没有不确定common::...
符号中的"copies"或"versions"用过的
通过程序:它只使用 libfoo.so
.
为什么?仅仅因为 libfoo.so
是 link 时代第一个 提供定义的 图书馆
对于 common::...
符号。如果我们将linkprog
与-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。
仍然没有不确定性,对程序没有影响,因为libfoo.so
和 libbar.so
link 从同一个对象文件 common.o
.
common::...
定义
linker 不会尝试查找符号的多个定义。一旦找到一个 输入目标文件或共享库中符号 S 的定义,它将引用绑定到 S 到它找到的定义,并通过解析 S 完成。它确实 不在乎它以后找到的共享库是否可以提供 S 的另一个定义,相同或不同, 即使后来的共享库解析符号 other 而不是 S.
导致多重定义错误的唯一方法是强制 linker
到静态link多个定义,即强制它物理合并到输出二进制文件中
两个 目标文件 obj1.o
和 obj2.o
都包含定义 S。
如果这样做,竞争的静态定义将具有完全相同的状态,并且仅
该程序可以使用一个定义,因此 linker 必须让您失望。但它不需要注意到一个
共享库 提供的 S 的动态符号定义如果它已经解析了 S,但它没有这样做。
[1] 当然,如果您使用不同的预处理器、编译器或 linkage 选项编译 link
lib1
和 lib2
,您可以破坏 "common" 功能任意程度。