2013-10-15 91 views
74

我想了解當全局變量和靜態變量模塊動態鏈接到應用程序時會發生什麼。 通過模塊,我指的是解決方案中的每個項目(我在Visual Studio中工作過很多!)。這些模塊內置於* .lib或* .dll或* .exe本身。動態鏈接時共享庫中的全局變量和靜態變量會發生什麼變化?

我明白,一個應用程序的二進制包含所有數據段的各個翻譯單元(目標文件)的全局和靜態數據(如果const的只讀數據段)。

  • 當這個應用程序使用帶有加載時動態鏈接的模塊A時會發生什麼?我假設DLL有一個全局和靜態的部分。操作系統是否加載它們?如果是這樣,他們在哪裏裝載?

  • 當應用程序使用與運行時動態鏈接一個模塊B會發生什麼?

  • 如果我的應用程序中有兩個模塊都使用A和B,那麼是如下所述創建的A和B的全局變量的副本(如果它們是不同的過程)?

  • 待辦事項的DLL A和B可以訪問應用程序的全局變量?

(請說出你的理由,以及)

MSDN報價:

變量聲明爲在DLL源代碼文件中全局編譯器作爲全局變量處理,鏈接器,但加載給定DLL的每個進程都會獲得該DLL全局變量的副本。靜態變量的範圍僅限於聲明靜態變量的塊。因此,默認情況下,每個進程都有自己的DLL全局和靜態變量實例。

和從here

當動態鏈接模塊,也可以是不同的不清楚是否庫有自己的全局的實例或全局是否是共享的。

謝謝。

+0

*模塊*你可能是指* libs *。有一個建議,在C++標準中增加* modules *,並且對於模塊的定義和截至目前的常規庫有着更爲精確的定義。 –

+0

啊,應該澄清一點。我將解決方案中的不同項目(我在Visual Studio中工作很多)視爲模塊。這些模塊內置到* .lib或* .dll中。 – Raja

+0

@DavidRodríguez-dribeas術語「模塊」是獨立(完全鏈接的)可執行文件的正確術語,包括:可執行程序,動態鏈接庫(.dll)或共享對象(.so)。這裏非常合適,意思是正確的,並且很好理解。正如我所解釋的,在有一個名爲「模塊」的標準功能之前,其定義仍然是傳統的功能。 –

回答

120

這是Windows和Unix類系統之間一個非常著名的差異。

不管是什麼:

  • 每個過程有它自己的地址空間,這意味着永遠不會在進程間共享的任何內存(除非你使用一些進程間通信庫或擴展)。
  • 一個定義規則(ODR)仍然適用,這意味着你只能有全局變量可見在鏈接時(靜態或動態鏈接)的一個定義。

所以,問題的關鍵是在這裏真的知名度

在所有情況下,static全局變量(或函數)在模塊外部(dll/so或者可執行文件)永遠不可見。 C++標準要求它們具有內部鏈接,這意味着它們在定義它們的翻譯單元(它成爲目標文件)之外是不可見的。所以,這解決了這個問題。

它變得複雜的是當你有全局變量extern。在這裏,Windows和類Unix系統完全不同。

對於Windows(.exe和.dll),extern全局變量不是導出符號的一部分。換句話說,不同的模塊根本不知道其他模塊中定義的全局變量。這意味着如果您嘗試創建一個應該使用DLL中定義的變量的可執行文件(因爲這是不允許的),您將會收到鏈接程序錯誤。您需要提供一個帶有該extern變量定義的目標文件(或靜態庫),並將其與靜態鏈接,這兩個文件均爲可執行文件和DLL,從而生成兩個不同的全局變量(一個屬於可執行文件,一個屬於DLL)。

要實際輸出一個全局變量在Windows中,你必須使用類似功能導出/導入語法中的語法,即:

#ifdef COMPILING_THE_DLL 
#define MY_DLL_EXPORT extern "C" __declspec(dllexport) 
#else 
#define MY_DLL_EXPORT extern "C" __declspec(dllimport) 
#endif 

MY_DLL_EXPORT int my_global; 

當你這樣做,全局變量被添加到列表可以像所有其他功能一樣鏈接導出的符號。

對於類Unix環境(如Linux),動態庫(稱爲「共享對象」,擴展名爲.so)導出所有extern全局變量(或函數)。在這種情況下,如果你從任何地方連接到共享目標文件,那麼全局變量是共享的,即作爲一個鏈接在一起。基本上,類Unix系統的設計使其與靜態或動態庫鏈接幾乎沒有區別。同樣,ODR全面應用:全局變量將在模塊之間共享,這意味着它應該只在加載的所有模塊中具有一個定義。

最後,在兩種情況下,Windows或Unix類系統中,你可以做運行時聯的動態庫的,即無論是使用LoadLibrary()/GetProcAddress()/FreeLibrary()dlopen()/dlsym()/dlclose()。在這種情況下,您必須手動獲取您希望使用的每個符號的指針,其中包含您希望使用的全局變量。對於全局變量,只要全局變量是導出符號列表的一部分(按照前面段落的規則),就可以使用GetProcAddress()dlsym(),就像您對函數做的一樣。

當然,作爲必要的最後說明:應該避免全局變量。我相信你所引用的文本(關於事物「不清楚」)完全是指我剛剛解釋的特定於平臺的差異(動態庫並非真正由C++標準定義,這是特定於平臺的領域,意味着它更不可靠/便攜)。

+2

很好的答案,謝謝!我有一個後續:因爲DLL是一個自包含的代碼和數據,它是否有一個類似於可執行文件的數據段部分?我試圖瞭解何時以及如何在使用共享庫時加載(到)數據。 – Raja

+8

@Raja是的,該DLL有一個數據段。實際上,就文件本身而言,可執行文件和DLL實際上是相同的,唯一真正的區別是在可執行文件中設置了一個標誌,表示它包含「main」函數。當進程加載DLL時,其數據段被複制到進程的地址空間中,並且靜態初始化代碼(它將初始化非平凡的全局變量)也在進程的地址空間中運行。加載與可執行文件的加載相同,只是進程地址空間被擴展而不是創建一個新的。 –

+0

謝謝澄清。 – Raja