msvc C++編譯連結

2023-07-19 21:01:07

C++編譯連結

靜態庫編譯

C RunTimeLibrary

C++是C的超集,C RunTimeLibrary 是 C 標準庫,在編譯期安裝的時候,或者下載vc執行時庫安裝到電腦中。

msvc中/mt /mtd /md /mdd 是決定當前程式用哪個C RunTimeLibrary. 不同的實現不同。

連結過程

靜態庫連結過程是需要所有的lib檔案。比如 A 靜態庫有 Hello 函數。 B 靜態庫使用 A 專案的 Hello 函數編譯成 Hello World 函數,C 執行程式呼叫 B 的Hello World函數。

A.lib 會包含Hello函數的資訊。

B.lib 會包含HelloWorld函數的資訊。但沒有Hello的函數。

C.exe 要連結exe的時候,需要 A.lib B.lib 才能真正連結成功。

當然還有一些預設的,比如 C RunTimeLibrary 的連結,也會參與進來。

當A.lib的 C RunTimeLibrary 與 B.lib 中C RunTimeLibrary的版本不匹配的時候,會報 error LNK2038: 檢測到「RuntimeLibrary」的不匹配項: 值「MT_StaticRelease」不匹配值「MTd_StaticDebug」。 (如果沒有呼叫 C 的函數,則不會報這個錯,最常見的 #include ... 但是基本函數,只要有一個包含了,就會報錯,所以必然出錯)

原因是 在連結的過程中,C的函數有多種實現導致。所以同一個函數,無法定位真正是哪個RunTimeLibrary中的函數參與進來。

動態庫編譯

動態庫編譯成dll,是跟exe一樣需要連結,連結一個可載入的動態庫。所以一個dll中,包含所有參照的函數資訊。

A.lib 靜態庫 b.lib b.dll 動態庫(動態庫專案也會生成一個lib符號檔案,但沒有實現,因為c++會在編譯期間,把函數和類改名。)

b.dll 中就包含 Hello 函數的資訊,當連結的時候,只需要連結 b.lib 即可。

c.exe 就只需要b.lib 和 b.dll 即可。

場景問題加深理解

下面場景都是使用了 RuntimeLibrary 庫的情景(基本沒有不用的)

  1. 因為exe必用 c 標準庫的東西,所以連結的時候如果 /MD /MDD /MT /MTD 如果不匹配,都會報 RuntimeLibrary 不一致

  2. 3rd_release.lib -> engine.lib -> app.exe 3rd_release.lib 如果是靜態庫,並且是release,沒有debug版本, 那 engine.lib 必須也是release版本的lib,否則在 app.exe 生成的時候會報 RuntimeLibrary 不一致。

  3. debug 是可以連結 release 版本的程式碼庫的,但是 debug A.exe 不能呼叫 額外的 debug B 庫,B 呼叫 release.lib 會報 A.exe 的RuntimeLibrary和 release.lib 的不一致。

  4. stl 不同debug release 的 許多變數的長度不一致,執行時會溢位閃退。

  5. *.lib 庫對應的宏和標頭檔案使用應保持一致,因為編譯時,已經根據宏 匯出函數內容到 .lib 中,如果標頭檔案宣告跟 lib 中函數不一致,會導致 函數 link 不上。

  6. class 的標頭檔案函數(實現在標頭檔案中), lib 中不會定義,基本大部分編譯期,會直接把標頭檔案函數變為行內函式,而且不用dllexport。

  7. 如果想使用 3rd_release.lib -> engine.lib -> app.exe engine 是debug庫,可以用 engine.dll ,這樣會在dll時候,就連結完成。

  8. 靜態庫函數保持一致,全域性變數,靜態變數都是一份。動態庫會在連結的時候,把靜態庫的內容連結到dll中,兩個dll連結一個靜態庫,則這個靜態庫中的全域性變數和靜態變數都是兩份。如果宏定義不一致,則會爆炸。(可以想象,一個庫改了靜態變數,而另一個庫的函數中沒有改,導致實現有時好,有時壞。 心態爆炸)

  9. vs 中如果不填 /md ... 等,填空,會預設 /mt /mtd (不一定,我測試是這樣)

  10. cpp中使用宏,標頭檔案不使用的庫,已經生成的lib 檔案,連結的主體(exe)再怎麼改 宏定義,也不會影響函數的實現(在編譯的過程已經確定了,連結方再怎麼改,也無濟於事)

  11. 網上查的dll使用準則是,dll中的物件,不能傳遞到dll外,如果傳出來,可能會呼叫到跟dll中預想不同的函數(因為dll的宏定義是在dll生成的時候,但是使用的人的dll和stl等標準庫的實現不一致),導致整體流程損壞而查不到原因。 dll的物件是在dll中運轉(減少錯誤發生率)

總結

如果沒有想好的話,最好是不要使用dll,靜態庫只是編譯慢,但會減少很多問題。 如果想要使用dll,則一定要注意專案連結和宏,還有物件的流轉,還有dll連結的問題。

最好是,如果一個dll連結了一個靜態庫,則不匯出這個靜態庫的任何標頭檔案,所有想要操作這個靜態庫的,都需要通過dll的介面來呼叫。

gcc clang 等編譯器,有些沒有_DEBUG 宏,當跨編譯器連結的時候,也可能會出現額外的問題。

而有些cmake工具會重寫宏定義,如果通過vs建立專案,有些會定義預設的宏定義,但是cmake等工具,是直接寫檔案。導致不一定會跟vs等IDE一樣生成預設的宏定義。則會導致編譯的問題。

目前C++專案第三方都是開源的,或者會給debug版本,所以大部分都是自己公司的某個人寫的(或者哪裡編譯出來的)才會導致只有release庫檔案。大多數是沒有這個問題的。