靜動態庫與項目管理
靜態庫與動態庫的比較
函數庫本質是一組函數,具有相近的功能或操作同一數據結構。根據鏈接時期的不同,函數庫分爲靜態庫和動態庫。
靜態庫是指編譯鏈接時,把庫文件的代碼全部加入到可執行文件中,因此生成的文件比較大,但在運行時也就不再需要庫文件了。其後綴名一般爲.a
。動態庫與之相反,在編譯鏈接時並沒有把庫文件的代碼加入到可執行文件中,而是在程序執行時由運行時鏈接文件加載庫,這樣可以節省系統的開銷,在編譯過程中僅簡單地引用函數聲明。動態庫一般後綴名爲.so
,如前面所述的 libc.so.6 就是動態庫。gcc
在編譯時默認使用動態庫。
靜態庫和動態庫的最大區別在於,在靜態情況下,把庫直接加載到程序中;而動態庫鏈接的時候,它只是保留接口聲明,將動態庫與程序代碼獨立,這樣就可以提高代碼的可複用度,和降低程序的耦合度。
- 靜態庫可以降低運行依賴,但是會增大程序體積,並且耦合性高。例如,100 份程序都用到靜態庫 a,靜態庫 a 會被拷貝 100 份。並且一旦靜態庫需要修改,需要修改 100 份。
- 動態庫提高代碼的複用度,減少程序體積,但是程序運行時需要安裝相應的動態庫。例如,100 份程序都用到動態庫 a,它們會共用 1 份動態庫 a,並且修改方便。但是需要運行程序時,系統必須安裝有動態庫 a。
gcc 介紹
Linux 系統下的 gcc 是 GNU 推出的功能強大、性能優越的多平臺編譯器。gcc 是可以在多種硬體平臺上編譯出可執行程序的超級編譯器,其執行效率與一般的編譯器相比平均效率要高 20%~30%。gcc 編譯器能將 C、C++語言源程序、匯程式化序和目標程序編譯、連接成可執行文件,如果沒有給出可執行文件的名字,gcc 將生成一個名爲 a.out 的文件。
gcc 編譯流程
gcc 編譯一個源代碼時,會依次經過預處理
-編譯
-彙編
-鏈接
這 4 個流程。
- 預處理,對源代碼中的文件包含、預編譯語句等進行分析,生成預編譯文件:
gcc -E hello.c -o hello.i
- 編譯,生成彙編文件:
gcc -S hello.i -o hello.s
- 彙編,生成目標文件:
gcc -c hello.s -o hello.o
- 鏈接,生成可執行文件:
gcc hello.o -o hello
gcc 參數表
參數 | 含義 |
---|---|
-E | 只預處理,不編譯,生成編譯代碼.i |
-S | 只編譯,不彙編,生成彙編代碼.s |
-c | 只編譯,不鏈接,生成目標文件.o |
-o file | 把輸出文件輸出到 file 裏 |
-v | 打印 gcc 的版本 |
-I dir | 在頭文件的搜索路徑列表中加入 dir 目錄 |
-L dir | 在庫文件的搜索路徑列表中加入 dir 目錄 |
-static | 顯示鏈接靜態庫(靜態庫也可以用動態庫的方式鏈接) |
-llibrary | 顯示鏈接名爲 library 的動態庫 |
靜態庫
靜態庫的名字是libxxx.a
,利用靜態函數庫編譯成的可執行文件比較大,因爲整個函數庫的全部代碼都會被整合進目標代碼中,他的優點就顯而易見了,即編譯後的執行程序不需要外部的函數庫支持,因爲所有使用的函數都已經被編譯進去了。當然這也會成爲他的缺點,因爲如果靜態函數庫改變了,那麼你的程序必須重新編譯。
靜態庫的代碼在編譯時鏈接到應用程序中,因此編譯時庫文件必須存在,並且需要通過-L
參數傳遞給編譯器,應用程序在開始執行時,庫函數代碼將隨程序一起調入進程內存段直到進程結束,其執行過程不需要原靜態庫存在。
生成靜態庫
在 UNIX 中,使用ar libxx.a xx.o
命令創建或者操作靜態庫。
參數 | 含義 |
---|---|
-r | 將目標文件插入靜態庫尾或者替換靜態庫中同名文件 |
-c | 創建靜態庫文件 |
使用靜態庫的例子
// add.h
#ifndef _ADD_H
#define _ADD_H
int add(int a, int b);
#endif
// add.cpp
#include "add.h"
int add(int a, int b)
{
return a + b;
}
// sub.h
#ifndef _SUB_H
#define _SUB_H
int sub(int a, int b);
#endif
// sub.cpp
#include "sub.h"
int sub(int a, int b)
{
return a-b;
}
// main.cpp
#include "add.h"
#include "sub.h"
#include <iostream>
int main()
{
std::cout << add(1, 2) << std::endl;
std::cout << sub(1, 2) << std::endl;
}
g++ -c add.cpp, g++ -c sub.cpp
,將源代碼編譯成.o
文件ar cr libmymath.a add.o sub.o
,將.o
文件生成靜態庫g++ main.cpp -L ./ -static -lmymath
,說明是靜態庫,默認是加載動態庫的- 或者
g++ main.cpp libmymath.a
,此時不用指定靜態庫
動態庫
動態庫的名字是libxxx.so
,相對於靜態函數庫,動態函數庫在編譯的時候 並沒有被編譯進目標代碼中,而是等到程序執行到相關函數時才調用該函數庫裏的相應函數,因此動態函數庫所產生的可執行文件比較小。由於函數庫沒有被整合進程序,而是程序運行時動態的申請並調用,所以程序的運行環境中必須提供相應的庫。動態函數庫的改變並不影響程序,所以動態函數庫的升級比較方便。
當需要載入動態庫代碼時,Linux 會按照某種路徑查找動態庫,一般有兩種方法。
- 帶編譯路徑,
g++ main.cpp -L ./ -lmymath
或者g++ main.cpp libmymath.so
- 更改環境變量,
LD_LIBPARY_PATH=./, export LD_LIBPARY_PATH
,但是這樣做,退出終端就失效了。 - 更改配置文件,將上面更改環境變量的方法寫入到
~/.bashrc
文件中。 - 還可以將自己的動態庫文件複製到
/usr/lib, /usr/local/lib
中。
生成動態庫
不同的 Linux 系統,鏈接動態庫方法,實現細節不一樣。編譯 PIC 型.o
中間文件的方法一般是採用 C 語言編譯器的-KPIC
或者-fpic
選項,有的 Linux 版本 C 語言編譯器默認帶上了 PIC 標準。創建最終動態庫的方法一般採用 C 語言編譯器的-G 或者-shared 選項。g++ -fPIC -shared -o libmymath.so add.cpp sub.cpp
參數 | 含義 |
---|---|
-shared | 指定生成動態連接庫 |
-fPIC | 編譯爲位置獨立的代碼 |
-L dir | 表示要連接的庫在 dir 目錄中 |
-llibrary | 顯示鏈接名爲 liblibrary.so 的動態庫 |
LD_LIBRARY_PATH | 動態庫的路徑的環境變量 |
使用動態庫的例子
源代碼同靜態庫。
g++ -fPIC -shared -o libmymath.so add.cpp sub.cpp
,生成動態庫g++ main.cpp -L ./ -lmymath
- 或者
g++ main.cpp libmymath.so
Makefile 項目管理
一個項目,它的程序大體步驟爲:
- 用編輯器編寫源代碼,如
.c
文件。 - 用編譯器編譯代碼生成目標文件,如
.o
文件。 - 用鏈接器連接目標代碼生成可執行文件,如
.exe
文件。
但如果一個項目源文件太多,一個一個編譯時就會特別麻煩,於是人們想到,爲什麼不設計一種類似批處理的程序,來批處理編譯源文件呢,於是就有了 make 工具,它是一個自動化編譯工具,你可以使用一條命令實現完全編譯。但是你需要編寫一個規則文件,make 依據它來批處理編譯,這個文件就是 makefile,所以編寫 makefile 文件也是一個程序員所必備的技能。
但對於一個大項目,編寫 makefile 實在是件複雜的事,於是人們又想,爲什麼不設計一個工具,讀入所有源文件之後,自動生成 makefile 呢,於是就出現了 cmake 工具,它能夠輸出各種各樣的 makefile 或者 project 文件,從而幫助程序員減輕負擔。但是隨之而來也就是編寫 cmakelist 文件,它是 cmake 所依據的規則。所以在編程的世界裏沒有捷徑可走,還是要腳踏實地的。所以總的流程如下: