当库以静态和动态方式链接时,静态类的变量和方法会发生什么情况



下面的escenario很吸引我。

(A)源代码库。

(B)是一个包含(A)的共享库

(C)一个包含(A)的静态库

(D),一个与(B)(C)链接的顶级程序。

以前我读过下面的答案,并理解如果链接是静态的,那么变量是真正的静态的,并且在整个程序中只存在一次,但如果链接是动态的,那么静态变量的存在次数与库包含的次数一样多。当库静态链接时,静态变量会发生什么

例如,如果我用以下代码包含N次静态库,则__immediately_lambda_call被触发N次,但访问相同的gMyClass

static MyClass& GetSingleton() {
static std::once_flag flag;
static MyClass* gMyClass;
std::call_once(flag, [&]() { gMyClass = new MyClass(); });
return *gMyClass;
}
static const auto __immediately_lambda_call = []() {
auto& gMyClass = GetSingleton();
return true;
}();

但是,当动态库和静态库包含相同的代码时会发生什么呢。

可以忽略下一段我更关心静态库和动态库之间如何共享内存。

问题是,顶级程序(D)在一个更复杂的代码中随机向我抛出了一个double free memory错误,我将其总结为以下代码,我不明白为什么,但在编译动态库时添加__attribute__((visibility( hidden ))似乎可以解决这个问题。

static const int gMyArr[2] = {1,2};
static const int gMyNum = 1;
class API_STATIC_DYNAMIC MyOtherClass
{
public:
MyOtherClass()
: mMatrix(new int[20])
{}

MyOtherClass()
{
delete[] mMatrix;
}

void doSomething()
{
int d = gMyArr[0];
}
static int gMatrix[10];
static int gNumber;

private:
int *mMatrix;
}

编辑库:

API.h

#if defined(STATIC_LIB)
#define API_STATIC_DYNAMIC 
#else
#define API_STATIC_DYNAMIC __attribute__((visibility("hidden"))) // removed "default"
#endif

MYLIB.h

class MyLibApi
{
public:
void doSomething();

private:
class MyLibImp;
MyLibImp *mPimpl;
}

MYLIB.cpp

class MyLibImp()
{
public:
MyOtherClass myOther;
}
void MyLibApi::doSomething()
{
mPimpl->myOther->doSomething();
}

编辑APP:

int main()
{
MyLibApi api;
api.doSomething();
MyOtherClass other;
other.doSomething();
return 0;
}
当库被静态和动态链接时,静态类的变量和方法会发生什么?

这是特定于操作系统(或实现(的

对于Linux,请阅读(相当长的(论文如何编写共享库以及程序库HowTo和C++dlopen minihowto。还可以阅读一本很好的C++编程书籍,也可以参阅本C++参考资料。稍后阅读C++11标准n3337(或更新的C++标准的规范(。

为了简化,在Linux上:

对于静态链接库:crt0在main之前运行静态数据的构造函数,在main之后运行静态数据析构函数一些构造函数初始化virtual方法所需的vtables。

对于共享库:静态数据的构造函数在dlopen(3(时间或ld.so(8(时间运行(因此在main之前(。析构函数在dlclose时间运行,或在主函数之后由ld.so运行。阅读execve(2(和其他相关系统调用(2(的详细信息。

构造函数运行的顺序可能与构建时链接库的顺序有关。如果您链接到g++ a.o b.o -lxa -lyb,则很可能静态构造函数在a.o中运行,然后在b.o中运行,再在libxa.a中运行,最后在libyb.so中运行,但也许共享库中的构造函数可以先运行。

您可以使用GDB调试器,并在您的构造函数的中的一些中设置断点。出于调试目的,您可能需要使用g++ -Wall -Wextra -g -O0 -fno-inline编译所有C++代码。


顶级程序(D(随机向我抛出一个双倍空闲内存

关于您的双重免费问题,请考虑使用valgrind和/或地址消毒剂。阅读有关调用GCC的更多信息。

当然,编译时要启用所有警告和调试信息,所以请在学习g++ -Wall -Wextra -g时使用。使用GCC 10,尝试其新的静态分析器选项。也可以考虑使用Clang静态分析仪或Frama-C。

我更关心静态库和动态库之间如何共享内存。

请记住,给定的进程只有一个虚拟地址空间。使用pmap(1(来理解它(或者通过编程,使用proc(5(,从程序内部/proc/self/maps(。阅读更多关于elf(5(的信息,并在您的(或其他(可执行文件和共享库上使用nm(1(、ldd(1(,file(1(和objdump(1(。还可以使用strace(1(、ltrace(1(和gdb(1(来了解程序的运行时行为。

最新更新