[英]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.然后我创建了两个库, libA
和libB
,都包含这个头文件。 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
被改变为PrintB
和FuncDefinedByLib
返回2而不是1。
Then I create a program that links to both of the libraries and calls PrintA
and PrintB
.然后,我创建了一个程序,它链接到两个库和调用PrintA
和PrintB
。 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所以这是我的问题
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?但是一个共享库在创建时应该已经在内部链接了(或者不是?我真的对共享库知之甚少),他们怎么知道其他库中有一个孪生类并在他们创建之后链接到那个类是自己创造的?
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.我不想总是为了共享一小段代码而拆分另一个共享库。
This behavior looks magical.这种行为看起来很神奇。 Does anyone utilize this behavior to do some good magical things?有没有人利用这种行为做一些好神奇的东西?
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 中的工作方式。
There are a few different solutions.有几种不同的解决方案。
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
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
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.