简体   繁体   English

当两个共享库定义相同的符号时,实际会发生什么?

[英]What actually happens when two shared libraries define the same symbol?

I recently encountered a crash issue when I linked two shared libraries (both made by myself) together.我最近在将两个共享库(均由我自己制作)链接在一起时遇到了崩溃问题。 I eventually found it was because of one source file duplicated between the two files.我最终发现这是因为两个文件之间重复了一个源文件。 In that source file a global std::vector was defined (in fact a static member of a class), and it ended up with being freed twice -- one by each library.在那个源文件中定义了一个全局 std::vector(实际上是一个类的静态成员),它最终被释放了两次——每个库一个。

I then wrote some test code to verify my thought.然后我写了一些测试代码来验证我的想法。 Here in a header I declare a class and a global object of this class:在标题中,我声明了一个类和这个类的全局对象:

#ifndef SHARED_HEADER_H_
#define SHARED_HEADER_H_

#include <iostream>

struct Data {
  Data(void) {std::cout << "Constructor" << std::endl;}
  ~Data(void) {std::cout << "Destructor" << std::endl;}
  int FuncDefinedByLib(void) const;
};

extern const Data data;

#endif

The FuncDefinedByLib function is left undefined. FuncDefinedByLib函数未定义。 I then created two libraries, libA and libB , both include this header.然后我创建了两个库, libAlibB ,都包含这个头文件。 libA looks like this libA看起来像这样

const Data data;

int Data::FuncDefinedByLib(void) const {return 1;}

void PrintA(void) {
  std::cout << "LibB:" << &data << " "
    << (void*)&Data::FuncDefinedByLib <<  " "
    << data.FuncDefinedByLib() << std::endl;
}

It defines the global data object, the FuncDefinedByLib function, and a function PrintA that prints the address of the data object, the address of FuncDefinedByLib , and the return value of FuncDefinedByLib .它定义了全球data对象时, FuncDefinedByLib功能和功能PrintA一个打印的地址data对象的地址FuncDefinedByLib和返回值FuncDefinedByLib

libB is almost same as libA except the name PrintA is changed to PrintB and FuncDefinedByLib returns 2 instead of 1. libB是因为几乎相同libA除了名称PrintA被改变为PrintBFuncDefinedByLib返回2而不是1。

Then I create a program that links to both of the libraries and calls PrintA and PrintB .然后,我创建了一个程序,它链接到两个库和调用PrintAPrintB Before encountering the crash issue I thought both libraries would create their own versions of class Data .在遇到崩溃问题之前,我认为两个库都会创建自己的class Data版本。 However, the actual output但实际输出

Constructor
Constructor
LibB:0x7efceaac0079 0x7efcea8bed60 1
LibB:0x7efceaac0079 0x7efcea8bed60 1
Destructor
Destructor

Indicates that both libraries use only one version of class Data and only one version of const Data data even if the class and the object are defined differently, which is from libA (I understand it is because libA is linked first).表示两个库只使用一个版本的class Data和一个版本的const Data data即使类和对象的定义不同,这来自libA (我理解这是因为libA链接了libA )。 And the double destruction clearly explains my crash problem.双重破坏清楚地解释了我的崩溃问题。

So here are my questions所以这是我的问题

  1. How does this happen?这是怎么发生的? I understand the main code linking against the two libraries may only link to the first symbol it sees.我知道链接两个库的主要代码可能只链接到它看到的第一个符号。 But a shared library should has been linked internally when it is created (or it is not? I really have no much knowledge of shared library), how can they know there is a twin class in other libraries and link to that when after they have been created on their own?但是一个共享库在创建时应该已经在内部链接了(或者不是?我真的对共享库知之甚少),他们怎么知道其他库中有一个孪生类并在他们创建之后链接到那个类是自己创造的?

  2. I know having duplicate code between shared libraries is generally a bad practice.我知道在共享库之间使用重复代码通常是一种不好的做法。 But is there a condition that by satisfying it duplication between libraries is safe?但是有没有条件通过满足库之间的重复是安全的? Or is there a systematic way to make my code duplicabale without risk?或者是否有一种系统的方法可以使我的代码无风险地重复? Or it is never safe and should always be strictly prohibited?或者它永远不安全,应该始终被严格禁止? I don't want to always split another shared library just to share a tiny piece of code.我不想总是为了共享一小段代码而拆分另一个共享库。

  3. This behavior looks magical.这种行为看起来很神奇。 Does anyone utilize this behavior to do some good magical things?有没有人利用这种行为做一些神奇的东西?

Part 1: About the Linker第 1 部分:关于链接器

This is a known problem in both C and C++, and it's the result of the current compilation model.这是 C 和 C++ 中的一个已知问题,它是当前编译模型的结果。 A full explanation of how it happens is beyond the scope of this answer, however this talk by Matt Godbolt provides an in-depth explanation of the process for beginners.对它如何发生的完整解释超出了这个答案的范围,但是Matt Godbolt 的这个演讲为初学者提供了对这个过程的深入解释。 See also this article on the linker .另请参阅有关链接器的这篇文章

There's a new version of C++ coming out in 2020, and it'll introduce a new compilation model (called Modules) that avoids problems like this. 2020 年将推出新版本的 C++,它将引入一个新的编译模型(称为模块)来避免此类问题。 You'll be able to import and export stuff from a module, similar to the way packages work in Java.您将能够从模块导入和导出内容,类似于包在 Java 中的工作方式。

Part 2: Solving your problem第 2 部分:解决您的问题

There are a few different solutions.有几种不同的解决方案。

Magical Solution 1: One unique global variable神奇的解决方案 1:一个唯一的全局变量

This one's pretty slick.这个很滑If you stick the global variable inside a function as a static variable, it will always get constructed only once, and this is ensured by the standard (even in a multithreaded environment).如果将全局变量作为静态变量粘贴在函数中,则它始终只会构造一次,这是由标准确保的(即使在多线程环境中)。

#ifndef SHARED_HEADER_H_
#define SHARED_HEADER_H_

#include <iostream>

struct Data {
  Data(void) {std::cout << "Constructor" << std::endl;}
  ~Data(void) {std::cout << "Destructor" << std::endl;}
  int FuncDefinedByLib(void) const;
};
Data& getMyDataExactlyOnce() {
    // The compiler will ensure
    // that data only gets constructed once
    static Data data;
    // Because data is static, it's fine to return a reference to it
    return data; 
}

// Here, the global variable is a reference
extern const Data& data = getMyDataExactlyOnce();

#endif

Magical Solution 2: Multiple distinct global variables, 1 per translation unit神奇的解决方案 2:多个不同的全局变量,每个翻译单元 1 个

If you mark a global variable as inline in C++17, then each translation unit that includes the header gets its own copy at it's own location in memory.如果您在 C++17 中将全局变量标记为内联,那么包含标头的每个翻译单元都会在其内存中的自己位置获得自己的副本。 See: https://en.cppreference.com/w/cpp/language/inline请参阅: https : //en.cppreference.com/w/cpp/language/inline

#ifndef SHARED_HEADER_H_
#define SHARED_HEADER_H_

#include <iostream>

struct Data {
  Data(void) {std::cout << "Constructor" << std::endl;}
  ~Data(void) {std::cout << "Destructor" << std::endl;}
  int FuncDefinedByLib(void) const;
};
// Everyone gets their own copy of data
inline extern const Data data;

#endif

Part 3: Can we use this to do Dark Magic?第 3 部分:我们可以用它来做黑魔法吗?

Kind of.的种类。 If you really, really wanna do Dark Magic with global variables, C++14 introduces templated global variables:如果你真的,真的想用全局变量做黑魔法,C++14 引入了模板化的全局变量:

template<class Key, class Value>
std::unordered_map<Key, Value> myGlobalMap; 

void foo() {
    myGlobalMap<int, int>[10] = 20;
    myGlobalMap<std::string, std::string>["Hello"] = "World"; 
}

Make of that what you will.做你想做的。 I haven't had much use for templated global variables, although I imagine if you were doing something like counting the number of times a function was called, or the number of times a type was created, it'd be useful to do this.我对模板化的全局变量没有太多用处,尽管我想如果你正在做一些事情,比如计算一个函数被调用的次数,或者一个类型被创建的次数,这样做会很有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 加载具有相同符号的两个共享库时是否存在符号冲突 - Is there symbol conflict when loading two shared libraries with a same symbol 当两个共享库依赖于以不同方式编译的第三方的相同版本时该怎么办? - What to do when two shared libraries depend on the same version of a third-party compiled differently? 当同一个命名空间中有两个具有相同签名的函数时会发生什么? - What happens when there are two functions with the same signature in the same namespace? 使用模板时实际发生了什么? - What actually happens when you use templates? 如何在2个不同的共享库中调用具有相同符号的函数? - How do you call a function with the same symbol in 2 different shared libraries? 使用make_shared时会发生什么 - What happens when using make_shared 当检查 for 循环中的这种类型的条件时,实际会发生什么? - What actually happens when this type of condition in the for loop is checked? 当两个库有同名的变量类型时,如何正确定义变量类型? - When two libraries have variable types with the same name, how to define the variable type correctly? 为实例变量(即对象)调用getter函数时,实际发生了什么? - What actually happens when calling a getter function for an instance variable(that is an object)? 在 C++ 中进行复制初始化时实际发生了什么? - What actually happens when copy initializing in C++?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM