动态库静态变量陷阱

    一、问题

        最近李四又遇到了一个动态库的问题,首先软件架构如下:

   动态库静态变量陷阱_第1张图片

    UI进程和后台进程都使用了同一个DLL。

   在DLL中有如下代码:

   export.h

#ifndef EXPORT_H
#define EXPORT_H
#include 
//#define  EXPORT_API 
namespace expdll
{
    class __declspec(dllexport) Singleton
    {
    public:
        static Singleton* getInstance();

        class GarbageCollector {
        public:
            ~GarbageCollector() {
                if (Singleton::instance) {
                    delete Singleton::instance;
                    Singleton::instance = 0;
                }
            }
        };

        void set(int i)
        {
            _print = i;
        }

        int get()
        {
            return _print;
        }


    private:
        Singleton() {}
        //把复制构造函数和=操作符也设为私有,防止被复制
        Singleton(const Singleton&) = delete;
        Singleton& operator=(const Singleton&) = delete;

        static Singleton* instance;
        static GarbageCollector gc;
        static std::mutex _lock;
        int _print = 0;

    };

    void __declspec(dllexport) ADD();
}

#endif

   export.cpp

#include "stdafx.h"
#include "export.h"

namespace expdll
{
    std::mutex expdll::Singleton::_lock;
    Singleton* Singleton::instance = nullptr;  

    Singleton* Singleton::getInstance()
    {
        _lock.lock();
        if (instance == NULL)
        {
            instance = new Singleton();
        }
        _lock.unlock();

        return instance;
    }
}
void ADD()
{
    int i = 0;
}

project1.exe

#include "../TestDLL/export.h"
#include 
#include        
#include 

int main()
{
    auto p_project = expdll::Singleton::getInstance();   
    p_project->set(100);
    
    std::cout << "地址:" << p_project << std::endl;
    int i = 0;
    while (i < 100)
    {
        std::cout << p_project->get() << " ";
        if ((i+ 1) %10 == 0) 
            std::cout << std::endl;

        std::chrono::milliseconds sd(1000);
        std::this_thread::sleep_for(sd);
        i++;
    }
    system("pause");
    return 0;
}

test.exe

#include "../TestDLL/export.h"
int main()
{
    auto test_p = expdll::Singleton::getInstance();
    test_p->set(200);

    std::cout << "地址:" << test_p << std::endl;    
    std::cout << test_p->get() << std::endl;


    system("pause");
	return 0;
}

    运行方法,先启动project1.exe再启动test.exe

    运行结果如下:

动态库静态变量陷阱_第2张图片

        可以看到在dll中static对象有了两份,指针地址不同,同时通过改指针对象里的值可以看出来两边互不影响,那么这个严格来说不符合完美单例要求,不同进程中出现了两个单例。

二、解决方法(待解决)

       李四想罪魁祸首就是每个进程都会为dll的静态变量创建内存空间,导致了两份的存在(也是保护机制,为避免数据被其他进程修改),思路就在于不创建两份,就能解决该问题。

      经过查找资料得知:

       转自:https://blog.csdn.net/lcfeng1982/article/details/78223616

        1、windows允许一个进程中有多个heap,那么这样就需要指明一块内存要在哪个heap上分配,win32的HeapAlloc函数就是这样设计的,给出一个heap的句柄,给出一个size,然后返回一个指针。每个进程都至少有一个主heap,可以通过GetProcessHeap来获得,其它的堆,可以通过GetProcessHeaps取到。同样,内存释放的时候通过HeapFree来完成,还是需要指定一个堆。

        2、这样的设计显然是比较灵活的,但是问题在于这样的话,每次分配内存的时候就必须要显式的指定一个heap,对于crt中的new/malloc,显然需要特殊处理。那么如何处理就取决于crt的实现了。vc的crt是创建了一个单独的heap,叫做__crtheap,它对于用户是看不见的,但是在new/malloc的实现中,都是用HeapAlloc在这个__crtheap上分配的,也就是说malloc(size)基本上可以认为等同于HeapAlloc(__crtheap, size)(当然实际上crt内部还要维护一些内存管理的数据结构,所以并不是每次malloc都必然会触发HeapAlloc),这样new/malloc就和windows的heap机制吻合了。(这里说的是vc的crt实现,我不知道其它crt实现是否如此)

     3、如果一个进程需要动态库支持,系统在加载dll的时候,在dll的启动代码_DllMainCRTStartup中,会创建这个__crtheap,所以理论上有多少个dll,就有多少个__crtheap。最后主进程的mainCRTStartup 中还会创建一个为主进程服务的__crtheap。(由于顺序总是先加载dll,然后才启动main进程,所以你可以看到各个dll的__crtheap地址比较小,而主进程的__crtheap比较大,当然排在最前面的堆是每个进程的主heap。)

     4、从上面的分析中可以看出,对于crt来说,由于每个dll都有自己的heap,所以每个dll通过new/malloc分配的内存都是在自己dll内部的那个heap上用HeapAlloc来分配的,而如果你想在其它模块中释放,那么在释放的时候HeapFree就会失败了,因为各个模块的__crtheap是不一样的。

三、结论     

       进程间的单例多份应该是正常的,模块中的单例多样应该是需要解决的,需要保证在同一个heap上创建,才能不出现错误,多份情况。

你可能感兴趣的:(c++编程)