編譯、連接、.h文件和.lib文件、.dll文件還有.o文件是什麼關係?他們各有什麼作用?

編譯、連接、.h文件和.lib文件、.dll文件還有.o文件是什麼關係?他們各有什麼作用?

還有在C語言裡面怎麼使用 .lib .dll、怎麼編寫lib?


h:頭文件,給編譯器用來檢查語法

lib:主要包含了如何找到函數的地址的信息,以及附帶一些編譯了一半的二進位數據

obj:編譯了一半的二進位數據

dll、exe:可以運行


曾經寫過一個系列的文章,希望有幫助:

  • 打通思路之編譯與鏈接(1)-編譯
  • 打通思路之編譯與鏈接(2)-目標文件
  • 打通思路之編譯與鏈接(3)-靜態鏈接
  • 打通思路之編譯與鏈接(4)-動態鏈接

主要參考書籍為《深入理解計算機系統》、《程序員的自我修養》,兩本都是極好的書。


《C程序設計伴侶》中的這張插圖,可以部分地回答你的問題

其中,從area.c到area.o的過程就是編譯,而.o文件是編譯的結果

在Windows平台上,靜態庫就是lib文件,動態庫就是dll文件如果還有疑問,可以參考書上的更詳細的介紹

《C程序設計伴侶》試讀與預訂


請服用《Advanced C and C++ Compiling》,包治好。下面是我的一點理解:

編譯,一般就是把源文件轉化成一個一個的二進位的目標文件。會留下一些坑,就是一些使用的其他文件的里的函數和變數會暫時不明確具體訪問地址,留給下一步解決。

鏈接,填編譯階段留下的坑,把沒有確定訪問地址變數函數都一個個設置好,然後把所有輸入的目標文件按照一定格式組成輸出文件,一般就是可執行文件或者庫文件,可執行文件不說,庫文件就是打包好的目標文件,給別人用的。

.h,實際上就是普通文件,特殊的後綴名而已,你自己用什麼後綴名無所謂,和.c沒什麼區別,include動作,實際上就是可以理解為copy過來,你也可以include .c啊。

.o ,編譯產生的目標文件,有一定格式的二進位文件,除了除了頭信息,debug信息之外,仔細觀察,實際上就是平台的機器碼。

.dll,動態庫,庫文件,可以理解裡面是具體的函數二進位打包,給其他可執行程序用的,好像,也有自己就能運行的。動態就是編譯的時候只指定lib路徑和聲明就行,不會實際組成可執行文件,實際運行的時候操作系統會load這些動態庫,給可執行程序使用。對應的是靜態庫,鏈接的時候就組成到可執行文件里,一般後綴.a,不用load.

就醬。


本人會做嵌入式,舉個c中最簡單的例子,現有以下代碼在main.c文件中:

int global;

int calculate()

{

int a,b,c;

a=1;

b=1;

c=a+b;

return c;

}

int main()

{

global=1;

calculate();

return 0;

}

一、編譯

把每個函數翻譯成可以運行的機器指令,但其中調用的函數和全局變數因為還沒有分配具體的物理地址,所以會先保留其符號,

main函數編譯後的大致內容如下:

命令0:將內核寄存器的值壓棧保存;

命令1:把global這個全局變數的值賦1;

命令2:程序跳轉至函數calculate;

命令3:出棧恢復main調用者的寄存器狀態,並把返回值0寫入指定的寄存器中;

命令4:返回;

calculate函數編譯後大致內容如下:

命令0:將內核寄存器的值壓棧保存,為局部變數a、b、c分配三個寄存器用以存放其數值;

命令1:變數a置1;

命令2:變數b置1;

命令3:計算a+b,並把計算結果賦給c;

命令4:出棧恢復calculate調用者的寄存器狀態,並把返回值c寫入指定的寄存器中;

命令5:返回;

二、鏈接

為所有的函數和全局變數分配一個實際的物理地址,並把編譯過程中沒有確定的函數和變數替換為具體的物理地址,例如可以:

把函數main的首地址安排在0x0地址上,把calculate函數的首地址安排在0x40地址上,把全局變數global安排在0x80地址上,這樣main函數編譯完後的命令1和命令2可以進一步改寫為:

命令1(改):把地址0x80的存儲內容置1;

命令2(改):程序跳轉至0x40繼續執行;

三、h文件

頭文件,本身是不包含程序的,它的意義在於告訴要從其他.c文件調用函數的人,他所用的函數的結構是什麼樣子的(當然還可以定義宏、結構體等),就是一個函數的介面聲明。比如開發者完成main.c後可以寫一個main.h來告訴想調用main.c中函數的人,其函數結構是什麼樣子的。

四、lib文件

庫文件,就是編譯後,未鏈接的代碼的集合,供其他開發者調用,通過匹配的h文件來告知開發者lib文件都有什麼函數,以及這些函數的介面都是什麼樣子的,lib有利於程序的模塊化,另外開發者不願公開的源代碼可以用lib的形式進行一定的保護(起碼反彙編是件很蛋疼的事情)。

四(附)

lib文件嚴格意義上講叫靜態庫文件,所有的代碼最終會在鏈接完成後整合成可執行文件(如.exe、.bin),但執行文件一旦生成,就與庫文件沒有關係了;大家可以理解為lib是房屋設計圖,執行文件就是蓋好的房子,房子不會因為設計圖的遺失而倒塌。

五、dll文件

動態鏈接庫,這個懂的不多,是pc中用到的,它和lib類似,也是存放函數機器碼的庫,但不同的是調用dll內函數的執行文件本身不包含這些函數的內容,當執行到相關的函數時,必須定位對應dll文件,將相關函數內容裝載到內存中再運行;就像家裡要炒個青菜,鍋碗調盆都準備完了,但每次燒之前要把青菜先買回來,才能開炒。

六、o文件

就是編譯完後的c文件,就是一過渡文件,對編譯原理不深究的人不必過多深入了解。

-----------------------------------------------------------------------------------------

本人PC程序寫得比較少,有不對之處還望指出,謝謝!


知乎首答,不請自來,歡迎大家批評指正。


寫在前面

  • 只討論C語言。
  • 「二進位代碼」——指被能夠計算機直接執行的代碼(至於究竟是操作系統層的命令,還是CPU的機器指令,這裡不做深入討論)。

頭文件(一般是.h文件)

  • 頭文件的功能在於指示編程人員和編譯器:哪些函數可以調用?這些函數應該怎麼調用(參數和返回值)?
  • 頭文件不指示任何庫的存儲位置。所有需要的庫文件,應位於默認目錄或指定目錄。

中間代碼文件(一般是.o文件或.obj文件)

  • 中間代碼文件包括:
    • 一般語句的二進位編碼。
    • 用戶定義函數的二進位編碼和介面。
    • 函數調用的指示(可以理解為「鏈接」或「指針」)。
  • 中間代碼文件的使命在鏈接完成後就結束了。

  • 庫是一個文件。
  • 庫的內容是形成庫的若干個中間代碼文件內容的集合。
  • 庫和中間代碼文件的格式不一樣。

靜態庫(Windows中稱為「靜態鏈接庫」)

  • 靜態庫本質上是一個庫。在Linux中一般為.a文件,在Windows中為lib文件。
  • 假設若干個可執行文件在鏈接時使用了相同的靜態庫。實際上,靜態庫產生的二進位編碼被放進了可執行文件中。當這些可執行文件運行的時候,每個可執行文件產生的進程,在內存中各有一份上述二進位編碼的副本。
  • 靜態鏈接庫的使命在鏈接完成後就結束了。

同一個可執行文件產生的進程在內存中共用一份代碼段。這是Linux操作系統內存管理的特性,與靜態庫無關。

共享庫(Windows中稱為「動態鏈接庫」)

  • 共享庫本質上是一個庫。在Linux中一般為.so文件,在Windows中為dll文件。
  • 假設若干個可執行文件在鏈接時使用了相同的共享庫。實際上,共享庫被可執行文件所引用,其二進位編碼並沒有被放進可執行文件中。當這些可執行文件運行時,被不同可執行文件使用的、同一個由共享庫產生的二進位編碼,在內存中只有一個副本。這個副本由所有可執行文件的所有進程共用。

靜態庫和共享庫的格式不同,兩者的大小不具有可比性。

編譯

  • 過程:由源文件生成中間代碼文件。
  • 命令:gcc
  • 選項:-c

打包靜態庫

  • 過程:用中間代碼文件生成靜態庫。
  • 命令:ar
  • 選項:-r

打包共享庫

  • 過程:用中間代碼文件生成共享庫。
  • 命令:gcc
  • 選項:-shared -fPIC
  • 注意:
    • 在編譯中間代碼文件時和打包共享庫時,都必須添加上述選項!否則打包會出錯。
    • 若不加-fPIC,則生成「偽共享庫」。「偽共享庫」既不會被放入可執行文件,又不能被多個引用其的可執行文件共享。

靜態鏈接

  • 過程:用中間代碼文件和靜態庫(可無)生成可執行文件。
  • 結果:
    • 可執行文件包含運行所需要的全部二進位代碼(包括被調用的系統函數的二進位代碼)。
    • 可執行文件一般可以獨立運行。
  • 命令:gcc
  • 選項:-static

動態鏈接

  • 過程:用中間代碼文件、靜態庫(可無)和共享庫(可無)生成可執行文件。
  • 結果:
    • 可執行文件包括中間代碼文件和靜態庫中的二進位代碼,這些二進位代碼來源於:a.基本語句;b.自定義函數的實現。
    • 可執行文件依賴於:鏈接時使用到的共享庫(所有層次的,全部的)。
    • 可執行文件一般不能獨立運行。
  • 命令:gcc
  • 選項:無

靜態編譯 = 編譯 + 靜態鏈接

動態編譯 = 編譯 + 動態鏈接

可以使用ldd命令來查看可執行文件所依賴的共享庫。

gcc命令的其他常用選項

  • -Wl,-rpath,./:可執行文件運行時,將所在目錄加入共享庫的查找範圍。
  • -include:查找頭文件並加入編譯。
  • -I(大寫的i):將目錄的所有頭文件加入編譯。
  • -l(小寫的L):查找庫並加入編譯(後面緊跟庫的縮寫)。
  • -L:將目錄加入庫的查找範圍。應將-L放在gcc命令的最後,否則某些時候會出錯。

GCC會自動地將一些頭文件和庫加入編譯和鏈接

  • 頭文件的默認查找目錄:當前目錄、/include/、/usr/include/
  • 庫的默認查找目錄:當前目錄、/lib/、/usr/lib/


都是編譯器乾的活,所以,我推薦你擼個簡單的編譯器,包治百病。

不信看圖


同推薦《程序員的自我修養—鏈接、裝載與庫》, 可以看看我寫的這篇文章:

C/C++編譯鏈接與裝載深入淺出

是我看完上面這本書, 還有&, &<深入理解計算機系統&>這三本書的編譯鏈接部分,綜合寫的, 總的來說比較適合科普, 也嘗試把知識有條理的串聯起來.


【編譯】使.c文件生成.o,其實 顧名思義compile,就是把C語言文本變成了計算機懂的機器碼。

【連接】使.o文件們之間的關係 [ 相互調用對方的包含的函數 ]有了體現,最終生成可執行文件。不過有時候有些函數的具體實現[ eg: printf() ]沒在你自己寫的.c們中,自然也不會出現在.o們中,它們其實是在各種連接庫(靜態連接庫lib、動態連接庫dll)中,所以這時我們連接時還要把這些庫的名字還有目錄信息告訴連接器並且同時習慣上為了方便,我們用.h單獨把這些庫中的函數什麼包含,而不是直接寫在.c們中,而且當.h不在默認目錄下時我們還有告訴連接器.h們具體目錄。

當然.h還有一個作用就是對於 macro[中文有多種翻譯:宏、巨集、大批量替換] 的使用。

好吧,其實一個簡明的說明(包括如何生產庫、調用庫)就是某個C語言編譯器的文檔,推薦tcc[Tiny C Compiler ,比gcc簡單有趣、體積小、win下即可使用]:Tiny C Compiler Reference Documentation


《深入理解計算機系統》第3頁


各路大神回答的都很清楚了,我也說下我的理解,描述上不一定嚴謹,只是闡述一下意思:

1. 先說下編譯。假設你已經寫好了一大堆的.c文件和.h文件,下面開始編譯了,此時跟連接、.lib文件、.dll文件還有.o文件沒關係,但是跟.h文件和.c文件有關係,因為要把它們編譯成機器自己認識的代碼,也就是機器代碼。

2. 再說說.o文件。編譯完了後,上述一大堆的.c和.h文件會被編譯為.o文件,此時跟編譯、.h文件和.lib文件、.dll文件也沒什麼關係了。.o文件一般就是機器代碼了,但是別以為此時就能執行這個文件了。上述.c文件里你肯定會調用好多的C標準庫函數,被編譯後,.o機器代碼里可沒有這些庫函數的代碼,所以還要把這些庫函數的代碼拉過來,這就是鏈接乾的事兒了;

3. 再說說鏈接。鏈接器會把編譯器編譯好的.o文件,以及.o文件中調用的庫函數代碼「合」到一起。在linux系統上,這些庫函數代碼就在.lib中,在windows系統上,這些庫函數代碼就在.dll中。

4. 經過上述步驟後,你寫的源代碼最終就變成一個可執行程序了,在windows上就是exe文件,在linux上就是.out文件了。

5. 所以,要搞清你說的這些術語之間的關係,可以用兩個簡單的線來描述,即:

你寫好的.c和.h源代碼——&>編譯——&>編譯為.o文件;

.o文件+lib文件或者.o文件+.dll文件——&>連接器——&>「合」在一起,變成一個可執行程序,如exe文件或者.out文件。


剛剛發現 .lib 居然。。。可用 7z 打開。。。裡面是 .txt+.dll 當然 有可能7z 解析不夠完整。


編譯、連接、.h文件和.lib文件、.dll文件還有.o文件是什麼關係?他們各有什麼作用?

編譯器實際上就是你所學的「語言」。所謂C語言 C++之類的主要實現就是靠編譯器。編譯器將你寫的程序編譯成中間文件,這個中間文件就是.o文件,當然有的系統擴展名不是這個,o是object的縮寫。這個文件包括了半成品的彙編,他包括了你的c文件所引用的頭文件,但是由於不知道最終地址,所以裡面所有關於地址的地方都是用代號來代替的。當所有文件編譯完成的時候,就會交給鏈接器,鏈接器負責把所有中間文件和庫文件,也就是.lib文件根據所在系統的內存映射規則進行放置,這個根據系統不同差異非常大,有幾百種之多。庫文件就是別人寫好的程序,但是不想給你開放,裡面實際上是.o狀態的代碼,這樣方便鏈接,然後出來就是hex文件了。這是Release版本,Debug還要加一些調試信息進去。.dll就是動態鏈接,你可以認為這是個別人的hex,但是他是可以在你程序的內存空間使用的別人的hex。大體就是這樣,初學者一般搞不清楚這些,等你啥時候自己寫個編譯器或者用命令行編譯一套代碼就懂了。

關於如何編寫,不知道你所用的系統,這個參照你用的IDE的介紹就行了,每種系統都是不一樣的,但是邏輯差不多


.h

1)說明全局變數的大小,幫住編譯器生成代碼

2)說明結構體的內存布局,編譯器有了這個就能從一個結構體指針訪問內部成員(內存布局確定,成員的地址偏移就能確定),從而生成代碼

3)函數的聲明明確調用規範(cdel、stdcall),編譯器有了這個也能知道壓棧的方式(ps:C語言早期不需要函數的前置聲明的,那麼在函數調用處根據參數進行壓棧,這是個坑)

4)C++中的類定義是結構體與函數聲明的結合,不贅述

obj

編譯器編譯後的代碼,但是外部符號的地址是未確定的,需要在鏈接時重定向。其中還包含了用於鏈接器的各種segment,以及符號導出信息和調試信息

lib

分為靜態庫和動態鏈接導入庫

靜態庫可以理解為obj的集合,不過參與生成的obj中segment會進行合併

導入庫記錄某個dll(可能exe有)中有哪些符合可供使用及其在模塊(exe或dll)中的偏移

dll

obj進行鏈接後的結果,有生成的機器碼並且此時外部符號地址要麼確定了,要麼已經知道其存在於某個模塊中

PS:以上基於代碼生成角度,沒有文件結構的說明(文件結構太複雜)

我對dll不了解,其他的也許說的不太對,如果題者不需要太詳細的windows下PE文件的結構知識,請參考《程序員的自我修養》一書


編譯,就是把源代碼翻譯成最終結果的中間產品(.o, .obj ),但是裡面有很多東西還沒有確定。

鏈接,就是把上述中間產品,鏈接成最終產品,把所有尚未確定的東西確定下來。

.h 文件,(包含了函數聲明,類聲明,常量定義,數據結構定義等)告訴編譯器,在某個地方存在有這些函數(編譯器並不關心這些函數具體位於何處),僅在編譯時有用。

.lib 文件,告訴鏈接器,在某個 dll 中哪些位置有哪些函數,僅在鏈接時有用。

.dll 連接後的最終產品(.exe 也是,同屬可執行文件),僅在運行時有用。

.lib 生成最終產品時產生的副產品(自動會生成的)。


你需要一本&<&&>


推薦閱讀:

非計算機專業學生學完二級水平的C語言,不想放棄這塊,有什麼可以建議?
軟體的版本號是如何確定的?
電腦資料備份到移動硬碟上,能否有一種工具可以不用完全刪除,再複製,而是只備份那些新增的,變更的,就是保持自己電腦上的數據和移動硬碟里的數據同步?
如何快速入門git和github?
乾貨丨玩轉 AWS 的必備工具

TAG:計算機 | C編程語言 | C | 作業 | CC |