c# - “靜態鏈接”和“動態鏈接”是什麼意思?




c++ linker static-linking dynamic-linking (5)

我經常聽到“靜態鏈接”和“動態鏈接”這兩個術語,通常涉及用CC++C#編寫的代碼,但我對這兩個術語都不太了解。 他們是什麼,他們到底在說什麼,他們鏈接的是什麼?


Answers

我認為這個問題的一個很好的答案應該解釋什麼是鏈接。

當你編譯一些C代碼時(例如),它被翻譯成機器語言。 只是一串字節,運行時會導致處理器添加,減去,比較,“轉到”,讀取內存,寫入內存等等。 這些東西存儲在對象(.o)文件中。

現在,很久以前,計算機科學家發明了這個“子程序”的東西。 執行 - 這 - 塊-的代碼和回報 - 在這裡。 不久之後,他們意識到最有用的子程序可以存儲在一個特殊的地方,並被任何需要它們的程序使用。

現在在早期,程序員必須打入這些子程序所在的內存地址。 類似於CALL 0x5A62 。 如果這些內存地址需要改變,這是乏味且有問題的。

所以,這個過程是自動的。 你編寫一個調用printf()的程序,編譯器不知道printf的內存地址。 因此,編譯器只是寫CALL 0x0000 ,並向對象文件添加一條註釋,說“必須用printf的內存位置替換此0x0000”。

靜態鏈接意味著鏈接器程序(GNU one被稱為ld )將printf的機器代碼直接添加到您的可執行文件,並將0x0000更改為printf的地址。 這發生在您的可執行文件被創建時。

動態鏈接意味著上述步驟不會發生。 可執行文件仍然有一個註釋說“必須用printf的內存位置替換0x000”。 操作系統的加載程序需要找到printf代碼,將其加載到內存中,並在每次運行程序時更正CALL地址。

程序調用一些靜態鏈接的printf是很常見的(像printf這樣的標準庫函數通常是靜態鏈接的)和其他動態鏈接的函數。 靜態的“成為可執行文件的一部分”,動態文件在可執行文件運行時“加入”。

這兩種方法都有優點和缺點,並且操作系統之間存在差異。 但既然你沒有問,我會在這裡結束。


靜態鏈接的庫在編譯時鏈接在一起。 動態鏈接庫在運行時加載。 靜態鏈接將庫位打包到可執行文件中。 動態鏈接僅引用庫中的引用; 動態庫的位存在於其他位置,並可以稍後換出。


從源代碼(你寫的)到可執行代碼(你運行的代碼)有兩個階段(大多數情況下是折扣解釋代碼)。

首先是將源代碼轉換為目標模塊的編譯。

第二個是鏈接,它將對像模塊組合在一起形成一個可執行文件。

除了別的以外,您還可以將第三方庫包含在您的可執行文件中,而不必看到它們的源代碼(如用於數據庫訪問的庫,網絡通信和圖形用戶界面)或編譯不同語言的代碼C和彙編代碼),然後將它們鏈接在一起。

當您將文件靜態鏈接到可執行文件時,該文件的內容將在鏈接時包含在內。 換句話說,文件的內容被物理地插入到您要運行的可執行文件中。

當您動態鏈接時,可執行文件中會包含指向要鏈接文件的指針(例如,文件的文件名),並且鏈接時不包括該文件的內容。 只有當您稍後運行可執行文件時,才會購買這些動態鏈接的文件,並且它們僅被購買到可執行文件的內存副本中,而不是磁盤上的文件。

這基本上是延遲鏈接的一種方法。 還有一種延遲的方法(在某些系統上稱為後期綁定),除非實際嘗試調用其中的函數,否則不會引入動態鏈接的文件。

靜態鏈接的文件在鏈接時被鎖定到可執行文件,所以它們永遠不會改變。 由可執行文件引用的動態鏈接文件可以通過替換磁盤上的文件來更改。

這允許更新功能而無需重新鏈接代碼; 加載程序每次運行時都會重新鏈接。

這既好又不壞 - 一方面,它允許更容易的更新和錯誤修復,另一方面它可能導致程序停止工作,如果更新不兼容 - 這有時是負責可怕的“DLL地獄”,有些人如果您將動態鏈接的庫替換為不兼容的應用程序,則應用程序可能會中斷(順便說一句,這樣做的開發人員應該期望被追捕並嚴懲)。

作為一個例子 ,讓我們看一下用戶編譯他們的main.c文件以進行靜態和動態鏈接的情況。

Phase     Static                    Dynamic
--------  ----------------------    ------------------------
          +---------+               +---------+
          | main.c  |               | main.c  |
          +---------+               +---------+
Compile........|.........................|...................
          +---------+ +---------+   +---------+ +--------+
          | main.o  | | crtlib  |   | main.o  | | crtimp |
          +---------+ +---------+   +---------+ +--------+
Link...........|..........|..............|...........|.......
               |          |              +-----------+
               |          |              |
          +---------+     |         +---------+ +--------+
          |  main   |-----+         |  main   | | crtdll |
          +---------+               +---------+ +--------+
Load/Run.......|.........................|..........|........
          +---------+               +---------+     |
          | main in |               | main in |-----+
          | memory  |               | memory  |
          +---------+               +---------+

您可以在靜態情況下看到主程序和C運行時庫在鏈接時(由開發人員)鏈接在一起。 由於用戶通常不能重新鏈接可執行文件,因此他們被困在庫的行為中。

在動態情況下,主程序與C運行時導入庫(聲明動態庫中的內容但實際上沒有定義它的東西)鏈接。 即使實際代碼丟失,這也允許鏈接程序進行鏈接。

然後,在運行時,操作系統加載程序會將主程序與C運行時DLL(動態鏈接庫或共享庫或其他命名法)進行後期鏈接。

C運行時的所有者可以隨時放入一個新的DLL來提供更新或錯誤修復。 如前所述,這既有優點也有缺點。


(我不知道C#,但有一個VM語言的靜態鏈接概念很有趣)

動態鏈接涉及知道如何找到所需的功能,只有您的程序參考。 您的語言運行時或操作系統搜索文件系統,網絡或編譯代碼緩存中的一段代碼,與引用匹配,然後採取多種措施將其集成到內存中的程序映像中,例如重定位。 它們都是在運行時完成的。 它可以手動完成,也可以通過編譯器完成。 有能力更新與混亂的風險(即DLL地獄)。

靜態鏈接在編譯時完成,您可以告訴編譯器所有功能部件在哪裡,並指示它將它們集成在一起。 沒有搜索,沒有歧義,沒有重新編譯就無法更新。 您的所有依賴關係與您的程序映像完全相同。


對於未指定內存模型的語言,您正在編寫處理器體系結構指定的語言內存模型的代碼。 處理器可能會選擇重新排序內存訪問以獲得性能。 因此, 如果您的程序有數據競爭(數據競爭是指多個核心/超線程可以同時訪問同一內存),那麼您的程序不是跨平台的,因為它依賴於處理器內存模型。 您可以參考Intel或AMD軟件手冊了解處理器如何重新排序內存訪問。

非常重要的是,鎖(以及帶鎖的並發語義)通常以跨平台的方式實現......因此,如果您在沒有數據競爭的多線程程序中使用標準鎖,那麼您不必擔心跨平台內存模型

有趣的是,C ++的Microsoft編譯器已經獲取/釋放volatile的語義,這是一個C ++擴展,用於處理C ++中缺少內存模型的問題http://msdn.microsoft.com/en-us/library/12a04hfd(v=vs.80).aspx 。 但是,考慮到Windows只能在x86 / x64上運行,這並不是說很多(英特爾和AMD內存模型使得在語言中實現獲取/發布語義更容易和更高效)。





c# c++ linker static-linking dynamic-linking