C/C++編譯的過程:
預處理,展開頭文件,宏定義,條件編譯處理等。通過gcc -E source.c -o source.i 或是cppsource.c生成
編譯。這裏是一個下一的編譯意義,指的是將預處理後的文件翻譯成彙編代碼的過程。通過gcc -S source.i生成,默認生成source.s文件。
彙編。彙編即將上一步生成的彙編代碼翻譯成對應的二進制機器碼的過程。通過gcc -c source.s來生成source.o文件
鏈接。鏈接是將生成目標文件和其引用的各種符號等生成一個完整的可執行程序的過程。鏈接的時候會進行虛擬內存的重定向操作。
編譯文件介紹:
.o文件,即目標文件,一般通過.c或是.cpp文件編譯而來,相當於VC編譯出來的obj文件
.so文件,shared object共享庫(對象),相當於windows下的dll
.a文件,archive歸檔包,即靜態庫。其實質是多個.o文件打包的結果,相當於VC下的.lib文件
.la文件,libtool archive文件,是libtool自動生成的共享庫文件
動態鏈接庫
動態鏈接庫是動態庫的一種(先這麼區分吧,因爲靜態庫也能動態加載),我們也習慣叫它共享庫(Shared Library),當程序加載進內存的時候,動態加載庫也會跟着被加載進內存。當動態加載庫加載到內存之後,如果後面的程序也起來了,而且也依賴這個動態加載庫的話,就不會重複加載。
動態鏈接庫相對於靜態庫來說更加靈活和複雜,因爲在實際應用的時候對動態鏈接庫會有以下要求:
- 更新動態庫之後,依然需要支持那些需要依賴舊版本動態庫的程序的正常運行。
- 當程序運行的時候,需要允許覆蓋特定的庫,甚至特定的函數。
- 在程序使用現有庫運行時,依然能夠需要支持以上兩點。
爲了達到以上目的,業界制定了一套規範,這套規範主要從兩個方面着手:
- 命名規範。一個動態庫會有不同的名字,他們分別起到了不同的作用。
- 路徑規範。一個動態庫要放在特定路徑下,內核才能夠在加載的時候去這個特定路徑找到這個動態鏈接庫。
命名規範
一個庫有三個分別起到不同作用的名字:soname,real name,link name。
soname
其實就是Shared Object NAME,這個名字的規範就是lib+庫名+so+大版本號,它是用來標示動態鏈接庫的主版本的,用於給內核加載動態庫選擇版本時提供參考。
例如:庫名叫做ssl,然後當前版本號是1,那麼它的soname就應該是 libssl.so.1
real name
命名規範就是soname+小版本號。如libssl.so.1爲例,它的real name就是soname再加小版本號,可以是libssl.so.1.0或者libssl.so.1.1這樣。
我們在build一個可執行文件的時候,需要在可執行文件裏面記錄這個可執行文件依賴於哪些動態庫,這樣內核在加載可執行文件的時候,才知道有哪些動態庫需要加載。在寫這條編譯命令的時候,是不需要帶版本號的。
如soname是libssl.so.1的庫子,它的link name就是libssl.so。編譯時可以加上"-lssl",當然如果該庫路徑不在默認編譯搜索路徑下還需要加上鍊接該庫的路徑信息,加入該庫位於當前路徑下,那麼加上"-L."。
庫命名使用規範
一般而言,如果你更新的動態庫內容變化並沒有添加或刪除API,只是修改了API的實現,那麼soname可以不用變,只要改變你的動態庫的real name就好。然後把對應soname做符號鏈接到你的新版本動態庫中,下次啓動這個可執行文件時,內核就會加載到最新的動態庫了。
如果你更新的動態庫裏面有添加新的API,可以再在soname後面添加一個小版本號,soname就可以變成libssl.so.1.1。即使原來的可執行文件還是依賴於libssl.so.1,那也不影響使用。
如果你更新的動態庫已經不兼容舊版本了,那麼soname後面的數字就要改改了,比如改成libssl.so.2。
所以你一定要給你的動態庫在編譯的時候設置soname,不然soname就跟着文件名走了,到後面涉及版本管理的時候你就坑了。
二者的不同點在於代碼被載入的時刻不同。
- 靜態庫在程序編譯時會被連接到目標代碼中,程序運行時將不再需要該靜態庫,因此體積較大。
- 動態庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入,因此在程序運行時還需要動態庫存在,因此代碼體積較小。
動態庫的好處是,不同的應用程序如果調用相同的庫,那麼在內存裏只需要有一份該共享庫的實例。帶來好處的同時,也會有問題!如經典的DLL Hell問題,關於如何規避動態庫管理問題,可以參考庫命名使用規範。
nm命令
有時候可能需要查看一個庫中到底有哪些函數,nm命令可以打印出庫中的涉及到的所有符號。庫既可以是靜態的也可以是動態的。nm列出的符號有很多,常見的有三種:
- 一種是在庫中被調用,但並沒有在庫中定義(表明需要其他庫支持),用U表示;
- 一種是庫中定義的函數,用T表示,這是最常見的;
- 一種是所謂的弱態"符號,它們雖然在庫中被定義,但是可能被其他庫中的同名符號覆蓋,用W表示。
$nm libhello.h
ldd命令
ldd命令可以查看一個可執行程序依賴的共享庫.