【问题标题】:What happens to static class's variables and methods when libraries are statically and dinamically linked当库被静态和动态链接时,静态类变量和方法会发生什么
【发布时间】:2020-11-05 14:17:10
【问题描述】:

下面的场景让我很感兴趣。

(A)源代码库。

(B) 包含(A) 的共享库

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

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

以前我阅读了以下答案并理解,如果链接是静态的,则变量是真正的静态变量,并且在整个程序中只存在一次,但如果链接是动态的,则静态变量存在的次数与包含库的次数一样多。 What happens to static variables when libraries are statically linked

例如,如果我在下面的代码中包含 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();
}

编辑应用程序:

int main()
{
   MyLibApi api;
   api.doSomething();
   MyOtherClass other;
   other.doSomething();
   return 0;
}

【问题讨论】:

标签: c++ static shared-libraries static-libraries static-variables


【解决方案1】:

当库被静态和动态链接时,静态类的变量和方法会发生什么?

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

对于 Linux,请阅读(相当长的)论文 How to write shared libraries 以及 Program Library HowToC++ dlopen minihowto。另请阅读C++ programming book,另请参阅this C++ reference。稍后阅读 C++11 标准 n3337(或更新的 C++ 标准的规范)。

为了简化,在 Linux 上:

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

对于共享库:静态数据的构造函数在dlopen(3) 时间或ld.so(8) 时间运行(所以在main 之前)。析构函数在dlclose 时间运行,或者在ld.so 之后运行。阅读execve(2) 和其他相关syscalls(2) 的详细信息。

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

您可以使用GDB 调试器并在您的 个构造函数的一些 中设置断点。您可能希望编译所有您的 C++ 代码 with g++ -Wall -Wextra -g -O0 -fno-inline 以进行调试。


顶层程序(D)随机给我一个双倍空闲内存

关于您的双重免费问题,请考虑使用valgrind 和/或address sanitizer。阅读更多关于invoking GCC的信息。

当然,编译时启用所有警告和调试信息,所以使用 at learn g++ -Wall -Wextra -g。使用GCC 10,尝试其新的static analyzer 选项。还可以考虑使用Clang static analyzerFrama-C

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

请记住,给定的process 有一个virtual address space。使用pmap(1) 来理解它(或以编程方式,使用proc(5),从你的程序内部,/proc/self/maps)。阅读有关elf(5) 的更多信息,并在您的(或其他)可执行文件和共享库上使用nm(1)ldd(1)file(1)objdump(1)readelf(1)。也可以使用strace(1)ltrace(1)gdb(1) 来了解程序的运行时行为。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-20
    • 2012-12-11
    • 1970-01-01
    • 2011-03-16
    相关资源
    最近更新 更多