Linux 下的動態庫和靜態庫

函數庫

爲什麼要使用函數庫

比如我們平常使用比較多的標準庫,裏邊就包含了很多有用的函數,因此使用函數庫能夠使我們的開發更爲省力。同樣多種多樣的第三方庫也會給編程語言帶來極大的擴展性。

函數庫劃分

主要分爲動態庫和靜態庫。

靜態庫特點

  • 靜態庫會在編譯階段時被完全整合進代碼段中,因此生成的可執行文件會比較大
  • 但這種編譯後的執行程序不再需要函數庫的支持
  • 但同時靜態庫如果發生了改變,程序必須要重新編譯

動態庫特點

  • 與靜態庫不同,動態庫在編譯階段不完全被整合進代碼段中,因此此時產生的可執行文件比較小
  • 同時因爲函數庫沒有完全被整合進程序當中,程序執行到相關函數時才調用函數庫中的對應函數,因此程序的運行環境中必須要提供對應的庫
  • 但動態函數庫的改變並不會改變程序本身

庫的命名規則

  • Linux 中靜態庫的名字一般是 libxxx.a
  • Linux 中動態庫的名字一般是 libxxx.so
  • Windows 中靜態庫的擴展名一般是 lib
  • Windows 中動態庫的擴展名一般是 lib/dll,不過此時的 lib 與靜態庫中的 lib 有所區別

有時候庫的名字是 libxxx.so.major.minor,xxx 是該庫的名稱,major 是該庫的主版本號,minor 是該庫的副版本號

庫路徑

一般來說 Linux 系統中函數庫的存放路徑爲 /lib,/usr/lib,/usr/local/lib。

使用特點

  • 當要使用靜態庫時,連接器會找到程序所需的函數,然後將之拷貝到執行文件中,由於靜態庫的特點,因此一般連接成功,靜態庫就不再需要了
  • 但當要使用動態庫時,動態庫會在執行程序內留下一個標記,表示當程序執行時,需要首先載入這個庫
  • 當靜態庫和動態庫同名時,gcc 命令將優先使用動態庫

ldd

ldd 命令能夠打印依賴項的信息:

ldd  prints the shared objects (shared libraries) required by each program or shared object specified on the command line.

以之前編譯過的一個可執行程序爲例,看一下其中的庫依賴關係:

wood@ubuntu:/tmp/test$ file list 
list: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=04fdccad327a8c8db231590323544e895a3d446c, not stripped
wood@ubuntu:/tmp/test$ ldd list 
	linux-vdso.so.1 =>  (0x00007ffdf291c000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f38f312b000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f38f34f5000)

使用庫進行編譯

原函數爲:

#include <stdio.h>
#include <math.h>

int main()
{
    double x = 3.14;
    printf("sin(x) = %f\n",sin(x));
    return 0;
}

 結果爲:

wood@ubuntu:/tmp/test$ gcc main.c 
/tmp/ccUjDv2w.o: In function `main':
main.c:(.text+0x23): undefined reference to `sin'
collect2: error: ld returned 1 exit status
wood@ubuntu:/tmp/test$ gcc main.c -lm -static -o smain
wood@ubuntu:/tmp/test$ gcc main.c -lm -o dmain
wood@ubuntu:/tmp/test$ ./smain 
sin(x) = 0.001593
wood@ubuntu:/tmp/test$ ./dmain 
sin(x) = 0.001593
wood@ubuntu:/tmp/test$ ll
total 1012
drwxrwxr-x  2 wood wood    4096 Apr 16 22:39 ./
drwxrwxrwt 15 root root    4096 Apr 16 22:39 ../
-rwxrwxr-x  1 wood wood    8656 Apr 16 22:39 dmain*
-rw-rw-r--  1 wood wood     122 Apr 16 22:37 main.c
-rwxrwxr-x  1 wood wood 1007744 Apr 16 22:38 smain*

從結果也可以看出,使用靜態庫編譯出來的可執行程序要比使用動態庫編譯出來的可執行程序大得多。

動態庫和靜態庫構建

ar

使用命令 ar 能夠構建動態庫和靜態庫:

NAME
       ar - create, modify, and extract from archives

SYNOPSIS
       ar [-X32_64] [-]p[mod] [--plugin name] [--target bfdname] [relpos]
       [count] archive [member...]

DESCRIPTION
       The GNU ar program creates, modifies, and extracts from archives.  An
       archive is a single file holding a collection of other files in a
       structure that makes it possible to retrieve the original individual
       files (called members of the archive).

       The original files' contents, mode (permissions), timestamp, owner, and
       group are preserved in the archive, and can be restored on extraction.

       GNU ar can maintain archives whose members have names of any length;
       however, depending on how ar is configured on your system, a limit on
       member-name length may be imposed for compatibility with archive
       formats maintained with other tools.  If it exists, the limit is often
       15 characters (typical of formats related to a.out) or 16 characters
       (typical of formats related to coff).

       ar is considered a binary utility because archives of this sort are
       most often used as libraries holding commonly needed subroutines.

       ar creates an index to the symbols defined in relocatable object
       modules in the archive when you specify the modifier s.  Once created,
       this index is updated in the archive whenever ar makes a change to its
       contents (save for the q update operation).  An archive with such an
       index speeds up linking to the library, and allows routines in the
       library to call each other without regard to their placement in the
       archive.

       You may use nm -s or nm --print-armap to list this index table.  If an
       archive lacks the table, another form of ar called ranlib can be used
       to add just the table.

       GNU ar can optionally create a thin archive, which contains a symbol
       index and references to the original copies of the member files of the
       archive.  This is useful for building libraries for use within a local
       build tree, where the relocatable objects are expected to remain
       available, and copying the contents of each object would only waste
       time and space.

       An archive can either be thin or it can be normal.  It cannot be both
       at the same time.  Once an archive is created its format cannot be
       changed without first deleting it and then creating a new archive in
       its place.

       Thin archives are also flattened, so that adding one thin archive to
       another thin archive does not nest it, as would happen with a normal
       archive.  Instead the elements of the first archive are added
       individually to the second archive.

       The paths to the elements of the archive are stored relative to the
       archive itself.

       GNU ar is designed to be compatible with two different facilities.  You
       can control its activity using command-line options, like the different
       varieties of ar on Unix systems; or, if you specify the single command-
       line option -M, you can control it with a script supplied via standard
       input, like the MRI "librarian" program.

靜態庫構建

  • 先將 xxx.c 文件生成 xxx.o 文件,命令爲:gcc -c xxx.c -o xxx.o
  • 將 xxx.o 文件打包爲 libxxx.a 靜態庫文件,命令爲:ar rc libxxx.a xxx.o
  • 將 libxxx.a 放到默認庫路徑,或者指定鏈接庫路徑
wood@ubuntu:/tmp/test$ ls
main.c  node.c  node.h 
wood@ubuntu:/tmp/test$ gcc -c node.c
wood@ubuntu:/tmp/test$ ls
main.c  node.c  node.h  node.o
wood@ubuntu:/tmp/test$ ar rc libnode.a node.o
wood@ubuntu:/tmp/test$ ls
libnode.a  main.c  node.c  node.h  node.o
wood@ubuntu:/tmp/test$ gcc main.c -L. -lnode
wood@ubuntu:/tmp/test$ ls
a.out  libnode.a  main.c  node.c  node.h  node.o
wood@ubuntu:/tmp/test$ ./a.out 
insert a node from tail
...
wood@ubuntu:/tmp/test$ mv libnode.a libnode.a.bak
wood@ubuntu:/tmp/test$ ./a.out 
insert a node from tail
...

動態庫構建

  • 先將 xxx.c 文件生成 xxx.o 文件,命令爲:gcc -c xxx.c -o xxx.o
  • 將 xxx.o 文件打包爲 libxxx.a 靜態庫文件,命令爲:gcc -fPIC -c xxx.c 和 gcc -shared -o libxxx.so xxx.o( -fPIC 表示編譯爲位置獨立的代碼,用於編譯共享庫。目標文件需要創建爲位置無關碼。也就是說可執行程序在加載該庫的時候,該庫可以位於可執行程序內存中的任何地方)
  • 將 libxxx.so 放到默認庫路徑,或者指定鏈接庫路徑
wood@ubuntu:/tmp/test$ ls
main.c  node.c  node.h
wood@ubuntu:/tmp/test$ gcc -fPIC -c node.c
wood@ubuntu:/tmp/test$ ls
main.c  node.c  node.h  node.o
wood@ubuntu:/tmp/test$ gcc -shared -o libnode.so node.o
wood@ubuntu:/tmp/test$ ls
libnode.so  main.c  node.c  node.h  node.o
wood@ubuntu:/tmp/test$ gcc main.c -L. -lnode
wood@ubuntu:/tmp/test$ ls
a.out  libnode.so  main.c  node.c  node.h  node.o
wood@ubuntu:/tmp/test$ ./a.out 
insert a node from tail
...
wood@ubuntu:/tmp/test$ mv libnode.so libnode.so.bak
wood@ubuntu:/tmp/test$ ls
a.out  libnode.so.bak  main.c  node.c  node.h  node.o
wood@ubuntu:/tmp/test$ ./a.out
./a.out: error while loading shared libraries: libnode.so: cannot open shared object file: No such file or directory

鏈接路徑

其實函數庫一般不是放在工作路徑使用的,而是放在默認路徑或者其他路徑然後指定路徑進行鏈接。

鏈接時如果出現 "xxx undefined" 的錯誤,說明找不到某個函數,大概率就是鏈接時找不到對應的動態函數庫,鏈接動態庫的路徑爲:

  • 默認路徑:/lib,/usr/lib,/usr/local/lib
  • -L 編譯時指定庫的位置,-l 編譯時指定庫的名字
  • 環境變量 LD_LIBRARY_PATH 指定鏈接路徑

運行路徑

運行時如果出現 "error while loading shared libraries" 的錯誤,說明在加載時找不到對應的動態庫:

  • 修改配置文件 /etc/ld.so.conf 中指定的動態庫搜索路徑
  • 環境變量 LD_LIBRARY_PATH 指定的動態庫搜索路徑
  • 默認的動態庫搜索路徑 /lib,/usr/lib
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章