同事找我看一个问题,一个访问全局变量不符合预期的问题。
因为新工程中静态库动态库非常多,非常不利于分析问题。
再因为并不是一个业务逻辑问题,而是一个语言层面的问题,所以我单独抽象出产生问题的环境,简化问题,更容易分析。
刚开始,是一个方案,五个工程,能够复现问题。
然后继续缩减三个工程,依然能够复现问题。
三个工程分别为静态库A,DLL B,EXE C。三者的依赖关系为:B依赖A, C依赖A和B。
工程A的主要实现代码: int g_int = 0; // 全局变量 int CStaticClass::GetGlobalValue() { return g_int; } 工程B的主要实现代码: // DLL.H class DLL_API CDLL { } // DLL.CPP extern int g_int; CDLL::CDLL(void) { m_pMyClass = new CMyClass(); m_pStatic = new CStaticClass(); g_int = 1; } CDLL::~CDLL(void) { delete m_pMyClass; m_pMyClass = NULL; } CMyClass* CDLL::GetClassPtr() { return m_pMyClass; } 工程C的主要实现代码: extern int g_int; int _tmain(int argc, _TCHAR* argv[]) { g_int = 2; CDLL dll; CMyClass* pClass = dll.GetClassPtr(); int n = pClass->Get(); // 这里的n为2,即不是1,也不是0 }
使用最少的代码复现问题,可以将问题集中在更小的方面,便于分析。
工程还可以进一步简化,手动将静态库中的类CStaticClass在两个工程B, C中实现。
然后调试代码时,进入int n = pClass->Get(); 进入Get()函数实现里,我们可能看到进入的是EXE工程的实现代码。
虽然指针是从DLL中导出来的,但是调用的却是EXE中的实现代码。
为什么?因为DLL和EXE都是独立的可执行代码。
如果DLL导出了CMyClass类,且EXE中没有CMyClass的实现代码,自然会去调用DLL中的实现代码。
如果DLL没有导出CMyClass类,且EXE中有CMyClass类的实现代码,那么自然会调用EXE中的实现代码。
如果DLL导出了CMyClass类,且EXE中也有CMyClass类的实现代码,则链接的时候会报重复定义的错误。
所以,如果调用的是静态库中的类函数的实现,则自然使用DLL中的全局变量。
如果调用的是EXE中的实现,则自然是访问EXE中的全局变量。
问题的解决方案:
方案1、静态库中去掉全局变量,改用其他方式。
方案2、静态库改成动态库。
方案3、整个Solution保证只有一份静态库的实现。
个人觉得静态库有太多实现,总感觉不太安全,觉得静态库只有一份实现比较好。
如果有多份实现,最好用动态库。
如果感觉上面比较麻烦,静态库中最好不要有全局变量。