下面的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(来了解程序的运行时行为。