同事找我看一個問題,一個訪問全局變量不符合預期的問題。
因爲新工程中靜態庫動態庫非常多,非常不利於分析問題。
再因爲並不是一個業務邏輯問題,而是一個語言層面的問題,所以我單獨抽象出產生問題的環境,簡化問題,更容易分析。
剛開始,是一個方案,五個工程,能夠復現問題。
然後繼續縮減三個工程,依然能夠復現問題。
三個工程分別爲靜態庫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保證只有一份靜態庫的實現。
個人覺得靜態庫有太多實現,總感覺不太安全,覺得靜態庫只有一份實現比較好。
如果有多份實現,最好用動態庫。
如果感覺上面比較麻煩,靜態庫中最好不要有全局變量。