使用 GCC 進行 C/C++ 代碼編譯時,如果代碼中使用到了庫函數,需要使用 -l 選項指定該庫函數所在的庫。如:-lm、-lrt、-lpthread等。這種方式使用的是庫的縮寫。一個庫的文件名如果是:libxxx.so 或 libxxx.a,則可以使用 -lxxx 進行鏈接。這種規則很常見,但是缺點也很明顯。假設在一台 Linux 機器上,同時具有 libxxx.so 和 libxxx.a,GCC 會優先鏈接 libxxx.so。雖然,GCC 也提供了 -static 選項可以強制鏈接靜態庫。但是,這時候新的問題出現了,假設有兩個庫 x 和 y,他們都具有靜態庫和動態庫兩個版本。如果我想要鏈接 libx.so 和 liby.a,使用 -static 選項就無法滿足這個要求。我需要更加精細的控制,最好是直接根據文件名直接指定鏈接哪個版本的庫文件,就沒有任何歧義。
GCC 文檔的關於 -l 選項的描述沒有告訴我如何直接使用一個庫文件名。於是翻看 ld 的文檔。在關於 -l 選項的描述中,有這樣一段話:
If namespec is of the form ‘:filename’, ld will search the library path for a file called filename, otherwise it will search the library path for a file called ‘libnamespec.a’.
也就是說可以使用 -l:filename 的形式直接指定庫文件名。這個只是 ld 的選項,GCC 能不能直接使用還需要驗證。設計三個 .cpp 文件,分為 x.cpp y.cpp 和 main.cpp。
// x.cpp
#include <iostream>
void print_x()
{
std::cout << "x" << std::endl;
}
// y.cpp
#include <iostream>
void print_y()
{
std::cout << "y" << std::endl;
}
// main.cpp
void print_x();
void print_y();
int main()
{
print_x();
print_y();
}
使用如下 Makefile 進行編譯。x.cpp 編譯成 libx.so 和 libx.a,y.cpp 編譯成 liby.so 和 liby.a。main.cpp 與 libx.so 和 liby.a 編譯鏈接成 main.out。
all : main
x :
gcc -o libx.so -shared -fPIC x.cpp
gcc -o x.o -c x.cpp
ar crs libx.a x.o
y :
gcc -o liby.so -shared -fPIC y.cpp
gcc -o y.o -c y.cpp
ar crs liby.a y.o
clean :
rm -f *.out *.o *.so *.a
main : x y
gcc -o main.out main.cpp -Wl,-rpath=./ -lstdc++ -L. -l:libx.so -l:liby.a
能直接通過編譯,使用 ldd main.out 查看一下動態庫依賴:
linux-vdso.so.1 (0x00007ffe71ace000)
libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f1bdea54000)
libx.so => ./libx.so (0x00007f1bdea4f000)
libc.so.6 => /lib64/libc.so.6 (0x00007f1bde845000)
libm.so.6 => /lib64/libm.so.6 (0x00007f1bde769000)
/lib64/ld-linux-x86-64.so.2 (0x00007f1bdec7b000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f1bde74e000)
可以看到 libx.so 被動態鏈接,而 liby.a 被靜態鏈接,沒有顯示。執行 main.out,輸出結果也符合預期。
x
y
由此可見,-l:filename 能直接用於 GCC。這種方法除了控制鏈接的庫是靜態的還是動態的之外,還能用於控制庫的版本號。例如 libx.so 同時存在兩個版本 libx.so.1 和 libx.so.2 ,可以使用 -l:libx.so.1 指定版本號為 1 的庫。
