iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0

什麼是庫

庫是寫好的現有的,成熟的,可以復用的二進制檔案。現實中每個專案都要依賴很多基礎的底層庫(iostream, vector, sys/resource.h ...),不可能每個人的程式都從零開始。

本質上來說庫是一種可執行的二進制形式,可以被作業系統載入記憶體中執行。庫常用的有兩種:靜態庫(Linux的 .a、Windows的 .lib)和動態庫(Linux的 .so、Windows的.dll)。

靜態庫與動態庫簡介

靜態庫(Static Library)是在編譯時期鏈接到程式的binarary file。

動態庫(Dynamic Library)在運行時才載入到記憶體中。

靜態庫與動態庫比較

可以看到靜態庫包含在可執行文件中。而動態庫只需要在程序中創建一個符號表(庫代碼中引用的函數、變量),就可以在程式運作期間才載入到記憶體中。

使用靜態庫有兩個明顯的缺點:

1.增加應用程式的大小。如果應用程序包含多個執行文件,庫是可執行文件的一部分而浪費了太多空間。

2.修改/升級庫程式後,需要重新編譯/鏈接整個應用程式。這可能會對規模較大的專案造成編譯耗時的問題。

由於上述原因,通常在開發時會選擇動態庫而不是靜態庫。
但是,動態庫並不完美。對於開發人員來說,它們也有自己的缺陷——安裝時需要小心動態庫的安裝位置,以確保可執行文件能夠在運行時期找到鏈接庫。

語法介紹

$ {LIBRARY_OUTPUT_PATH}

定義:用來設定導出庫的路徑

add_library

定義:將原始碼編譯成不同格式的二進制檔

 add_library(<name> [STATIC | SHARED] [<source>...])

<name> : 編譯出的庫名稱。

[STATIC | SHARED] : 指定生成庫的格式,是靜態庫或是動態庫。
[<source>...] : 原始碼名稱。
P.S. 編譯出的庫系統會自動加上名稱,靜態庫為 libname.a,動態庫為 libname.so

target_link_libraries

定義:將執行檔與二進制檔鍊結起來

target_link_libraries(<target> ... <item>...)
target : 執行檔名稱
item : 要與執行檔鍊結的二進制檔案名稱

範例

$ git clone https://github.com/m11112089/2023_iT_CMake.git
$ cd ~/2023_iT_CMake/Day10

├── build
├── CMakeLists.txt
├── include
│   └── mysqrt.h
└── src
    ├── main.cpp
    └── mysqrt.cpp

CMakeLists.txt

  1. 編譯從原始碼直接編譯的版本,用於對照使用庫版本。
# 普通版本
include_directories(${PROJECT_SOURCE_DIR}/include)
add_executable(main src/main.cpp src/mysqrt.cpp)
  1. 設定靜態庫和動態庫的輸出位置到 build/lib 中(不設定也可以)。
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
# 設定庫的輸出路徑
  1. 將mysqrt.cpp編譯成靜態庫,並與main_a鍊結成執行檔。
# 靜態庫
add_library(mysqrt_a STATIC src/mysqrt.cpp)
target_include_directories(mysqrt_a PUBLIC ${PROJECT_SOURCE_DIR}/include)
add_executable(main_a src/main.cpp)
target_link_libraries(main_a mysqrt_a)
  1. 將mysqrt.cpp編譯成動態庫,並與main_so鍊結成執行檔。
# 動態庫
add_library(mysqrt_so SHARED src/mysqrt.cpp)
target_include_directories(mysqrt_so PUBLIC ${PROJECT_SOURCE_DIR}/include)
add_executable(main_so src/main.cpp)
target_link_libraries(main_so mysqrt_so)
  1. 進入build資料夾編譯並執行

$ cd build
$ cmake ..
$ make
$ ./main 10
$ ./main_a 10
$ ./main_so 10

編譯完之後,在build資料夾會生成以下檔案。

├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
├── lib
│   ├── libmysqrt_a.a
│   └── libmysqrt_so.so
├── main
├── main_a
├── main_so
└── Makefile
 

因為有設定過庫導出的位置(LIBRARY_OUTPUT_PATH)位於build/lib中,因此可以發現lib資料夾中有編譯出的libmysqrt_a.a靜態庫與libmysqrt_so.so與動態庫。

再來比較生成的執行檔大小,可以發現main和main_a檔案大小一模一樣都是24864 bytes,且比使用動態函式庫的main_so大。
這是因為動態庫與靜態庫的特性不同,靜態庫包含在可執行文件中。而動態庫只需要在程序運行期載入,因此執行檔案大小會不同。
https://ithelp.ithome.com.tw/upload/images/20230924/20162026I3Yd4SOb14.png

接下來我們把build/lib資料夾中的靜態庫與動態庫刪除。

├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
├── lib
├── main
├── main_a
├── main_so
└── Makefile

然後執行main_so會發現跳出錯誤訊息,提示找不到libmysqrt_so.so,但是執行main_a 卻能正常執行。

kai@esoc:~/2023_iT_CMake/Day10/build$ ./main_so 10
./main_so: error while loading shared libraries: libmysqrt_so.so: cannot open shared object file: No such file or directory

這是因為程式在執行期間才會去尋找動態庫載入到記憶體中。
這就是位什麼需要小心動態鏈接庫的安裝位置,以確保可執行文件能夠在運行時期找到鏈接庫。

Reference

C++ Development Tutorial 4: Static and Dynamic Libraries

書籍:程序员的自我修养—链接、装载与库


上一篇
[Day 9] 子目錄
下一篇
[Day 11] 庫的實際應用經驗--使用別人的庫
系列文
建構屬於自己的C/C++專案 : 我的30天CMake學習之旅29
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言