c++ - C++-如果两个库使用相同的源代码进行构建会发生什么

标签 c++ design-patterns shared-libraries token-name-resolution

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

我的问题是


有可能还是会给我错误?
如果可以成功构建,那么如何解决命名问题?
F.e.假设foocommon.cpp中的类。 foo_v1是lib1.so中foo的对象,而foo_v2是lib2.so中foo的对象。现在在APP辫子期间会发生什么?也有可能在APP应用程序中创建foo对象吗?

最佳答案

自然会建议您考虑构建共享的通用功能
通过lib1.solib2.so进入不同的共享库libcommon.so

但是,如果您仍然想静态链接通用功能
完全相同1
进入lib1.solib2.so,您可以将这两个共享库与
您的程序。链接器将不会有任何问题。这是一个
插图:

普通

#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.solibbar.so。的
我们需要的源文件是foo.cppbar.cppcommon.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.sofoo.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的定义。它链接了所有
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是链接中提供定义的第一个库
用于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.oobj2.o都包含定义S。
如果这样做,则竞争的静态定义将具有完全相同的状态,并且仅
程序可以使用一个定义,因此链接器必须使您失败。但这并不需要引起任何注意
由共享库提供的S的动态符号定义(如果它已经解析了S,但尚未这样做)。


[1]当然,如果使用不同的预处理器,编译器或链接选项编译和链接lib1lib2,则可以在某种程度上破坏“公共”功能。

关于c++ - C++-如果两个库使用相同的源代码进行构建会发生什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54786262/

相关文章:

c++ - 在 C++ 中连续流式传输多个文件

c++ - 无堆粉刺。不正确还是迷信?

java - "Execute Around"成语是什么?

c++ - 关于可能的设计模式的问题

android - Dalvik 正在寻找扩展名为 '.0' 的 .so 文件 - 为什么?

firefox - 计划为 Firefox 制作一个 SO 指示器

perl - 如何找到模块名称到不透明指针的 DynaLoader 映射?

c++ - 编写我自己的 iostream 实用程序类 : Is this a good idea?

.net - 在客户端服务器应用程序中推送实时数据的架构/模式

c++ - 查找 sizeof char 数组 C++