C++ 静态变量在.dll/.lib中显示不同的行为

C++ 静态变量在.dll/.lib中显示不同的行为,c++,visual-c++,dll,C++,Visual C++,Dll,我试图了解静态在dll/lib中是如何工作的,因此我做了以下测试: 创建三个项目: 1) libadd //has an class Object and a template class Single<T> 2) libuseadd //has a function useadd which will call // Single<Object>::instance in li

我试图了解静态在dll/lib中是如何工作的,因此我做了以下测试:

创建三个项目:

1) libadd              //has an class Object and a template class Single<T>
2) libuseadd           //has a function useadd which will call 
                       // Single<Object>::instance in libadd
3) app                 // main() will call 
                       // Single<Object>::instance in libadd and
                       // useadd in libuseadd
//=============libuseadd=======================

//add.h
#pragma once

#include <iostream>

class __declspec(dllexport) Object
{
    public:
        Object():cnt_(0){}
        void speak()
        {
            std::cout<<"object:"<<cnt_++<<std::endl;
        }
    private:
        int cnt_;
        
};

template<typename T>
class __declspec(dllexport) Single
{
public:
    static T& instance()
    {
        static T t;
        return t;
    }

};

//add.cpp
#include "add.h"
//useadd.h
#pragma once

__declspec(dllexport) void useadd();

//useadd.cpp
#include "useadd.h"
#include "add.h"

void useadd()
{
    Object& obj = Single<Object>::instance();
    obj.speak();
    
}
#include "add.h"
#include "useadd.h"

int main(int argc, char* argv[])
{
    Object& obj1 = Single<Object>::instance();
    obj1.speak();
    
    useadd();

    return 0;
}
//add.h
#pragma once

#include <iostream>


class __declspec(dllexport) Single
{
public:
    static int& instance()
    {
        static int t = 0;
        int tmp = t++;
        return tmp;
    }

};

//add.cpp
#include "add.h"
//useadd.h
#pragma once

__declspec(dllexport) void useadd();

//useadd.cpp
#include "useadd.h"
#include "add.h"

#include <iostream>
using namespace std;
void useadd()
{
    cout<< Single::instance() <<endl;
}
//main.cpp
#include "add.h"
#include "useadd.h"

using namespace std;
int main(int argc, char* argv[])
{
    cout << Single::instance() << endl;
    useadd();
    return 0;
}
//==================================main.cpp=====================

//add.h
#pragma once

#include <iostream>

class __declspec(dllexport) Object
{
    public:
        Object():cnt_(0){}
        void speak()
        {
            std::cout<<"object:"<<cnt_++<<std::endl;
        }
    private:
        int cnt_;
        
};

template<typename T>
class __declspec(dllexport) Single
{
public:
    static T& instance()
    {
        static T t;
        return t;
    }

};

//add.cpp
#include "add.h"
//useadd.h
#pragma once

__declspec(dllexport) void useadd();

//useadd.cpp
#include "useadd.h"
#include "add.h"

void useadd()
{
    Object& obj = Single<Object>::instance();
    obj.speak();
    
}
#include "add.h"
#include "useadd.h"

int main(int argc, char* argv[])
{
    Object& obj1 = Single<Object>::instance();
    obj1.speak();
    
    useadd();

    return 0;
}
//add.h
#pragma once

#include <iostream>


class __declspec(dllexport) Single
{
public:
    static int& instance()
    {
        static int t = 0;
        int tmp = t++;
        return tmp;
    }

};

//add.cpp
#include "add.h"
//useadd.h
#pragma once

__declspec(dllexport) void useadd();

//useadd.cpp
#include "useadd.h"
#include "add.h"

#include <iostream>
using namespace std;
void useadd()
{
    cout<< Single::instance() <<endl;
}
//main.cpp
#include "add.h"
#include "useadd.h"

using namespace std;
int main(int argc, char* argv[])
{
    cout << Single::instance() << endl;
    useadd();
    return 0;
}
如果结果是:

对象:0

对象:1

这意味着instance()函数中的静态变量只有一个实例(正确)

如果结果是:

对象:0

对象:0

那么这意味着instance()函数中的静态变量是重复的(错误!)

我发现结果取决于库是编译为dll还是lib。 当libadd和libuseadd都编译为dll时,它甚至取决于实例函数在.h或cpp中的实现。假设我们必须将add.h/add.cpp的值更改为:

//when libadd/libuseadd all are dll, we have to use the codes below to 
//make it work correctly.
template<typename T>
class __declspec(dllexport) Single
{
public:
    static T& instance();
};
template  class   Single<Object>;

//add.cpp
#include "add.h"

#include "add.h"

template<typename T>
T& Single<T>::instance()
{
    static T t;
    return t;
}
//当libadd/libuseadd都是dll时,我们必须使用下面的代码
//让它正常工作。
模板
类declspec(dllexport)单个
{
公众:
静态T&实例();
};
模板类单一;
//add.cpp
#包括“add.h”
#包括“add.h”
模板
T&Single::instance()
{
静态T;
返回t;
}

我想知道为什么

更新(删除模板和类对象):

//================libadd=======================

//add.h
#pragma once

#include <iostream>

class __declspec(dllexport) Object
{
    public:
        Object():cnt_(0){}
        void speak()
        {
            std::cout<<"object:"<<cnt_++<<std::endl;
        }
    private:
        int cnt_;
        
};

template<typename T>
class __declspec(dllexport) Single
{
public:
    static T& instance()
    {
        static T t;
        return t;
    }

};

//add.cpp
#include "add.h"
//useadd.h
#pragma once

__declspec(dllexport) void useadd();

//useadd.cpp
#include "useadd.h"
#include "add.h"

void useadd()
{
    Object& obj = Single<Object>::instance();
    obj.speak();
    
}
#include "add.h"
#include "useadd.h"

int main(int argc, char* argv[])
{
    Object& obj1 = Single<Object>::instance();
    obj1.speak();
    
    useadd();

    return 0;
}
//add.h
#pragma once

#include <iostream>


class __declspec(dllexport) Single
{
public:
    static int& instance()
    {
        static int t = 0;
        int tmp = t++;
        return tmp;
    }

};

//add.cpp
#include "add.h"
//useadd.h
#pragma once

__declspec(dllexport) void useadd();

//useadd.cpp
#include "useadd.h"
#include "add.h"

#include <iostream>
using namespace std;
void useadd()
{
    cout<< Single::instance() <<endl;
}
//main.cpp
#include "add.h"
#include "useadd.h"

using namespace std;
int main(int argc, char* argv[])
{
    cout << Single::instance() << endl;
    useadd();
    return 0;
}
//add.h
#布拉格语一次
#包括
类declspec(dllexport)单个
{
公众:
静态int&实例()
{
静态int t=0;
int tmp=t++;
返回tmp;
}
};
//add.cpp
#包括“add.h”
//=============libuseadd=======================

//add.h
#pragma once

#include <iostream>

class __declspec(dllexport) Object
{
    public:
        Object():cnt_(0){}
        void speak()
        {
            std::cout<<"object:"<<cnt_++<<std::endl;
        }
    private:
        int cnt_;
        
};

template<typename T>
class __declspec(dllexport) Single
{
public:
    static T& instance()
    {
        static T t;
        return t;
    }

};

//add.cpp
#include "add.h"
//useadd.h
#pragma once

__declspec(dllexport) void useadd();

//useadd.cpp
#include "useadd.h"
#include "add.h"

void useadd()
{
    Object& obj = Single<Object>::instance();
    obj.speak();
    
}
#include "add.h"
#include "useadd.h"

int main(int argc, char* argv[])
{
    Object& obj1 = Single<Object>::instance();
    obj1.speak();
    
    useadd();

    return 0;
}
//add.h
#pragma once

#include <iostream>


class __declspec(dllexport) Single
{
public:
    static int& instance()
    {
        static int t = 0;
        int tmp = t++;
        return tmp;
    }

};

//add.cpp
#include "add.h"
//useadd.h
#pragma once

__declspec(dllexport) void useadd();

//useadd.cpp
#include "useadd.h"
#include "add.h"

#include <iostream>
using namespace std;
void useadd()
{
    cout<< Single::instance() <<endl;
}
//main.cpp
#include "add.h"
#include "useadd.h"

using namespace std;
int main(int argc, char* argv[])
{
    cout << Single::instance() << endl;
    useadd();
    return 0;
}
//useadd.h
#布拉格语一次
__declspec(dllexport)void useadd();
//useadd.cpp
#包括“useadd.h”
#包括“add.h”
#包括
使用名称空间std;
void useadd()
{

尝试从DLL导出模板类型通常是不明智的。客户端代码可以使用它想要的任何类型,DLL作者无法预测它将使用哪些类型。这并不是禁止的,在某些情况下可能有用。一个很好的例子是
std::basic_string
,msvcp.DLL将char和wchar_____T专门化导出到实现t std::string和std::wstring。静态成员不可避免地会将其变成毒药,不要这样做。@AlexF谢谢!我删除了模板和类对象。但是您可以看到,我仍然可以复制奇怪的behavior@HansPassant感谢您的澄清!您知道为什么静态变量在dll和库中的行为不同吗?当您链接静态库时通常,每个模块都有自己的副本,并且保证不会工作。当您使用DLL时,它可能会工作,因为只有DLL模块将变量存储在其数据段中。强调可能,您必须正确猜测T。只有当您在模块bou的进程级别上获得运行时支持时,这才能真正达到良好的效果NDARES,仲裁如何存储变量。VM实现的特性,如JVM和CLR,而不是C++。“当你连接静态库时,每个模块都会得到自己的拷贝,并且保证不工作。”在我的测试中,如果libadd和libuseadd都是静态的,那么它可以工作,比如说,只有一个静态变量实例。你知道为什么吗?