靜態庫/動態庫的編譯和使用方法學習記錄

本文參考資料:《Linux編程一站式學習》 版權 © 2008, 2009 宋勁杉, 北京亞嵌教育研究中心
部份內容摘自此文
linux

有時候須要把一組代碼編譯成一個庫,這個庫在不少項目中都要用到,例如libc就是這樣一個庫,咱們在不一樣的程序中都會用到libc中的庫函數(例如printf),也會用到libc中的變量。
學習中用到的一個簡單的小程序,程序將字符a,b,c壓入堆棧stack[512],隨後再倒序輸出,打印出cba。爲了使用到的gcc命令更具備意義,特地將程序分開在了幾個C文件中,最後將幾個.o文件連接爲可執行文件。
stack.c編程

char stack[512];
int top = -1;

pop.c小程序

extern int top;
extern char stack[512];
char pop(void)
{
return stack[top--];
}

push.cide

extern int top;
extern char stack[512];
void push(char c)
{
stack[++top]=c;
}


empty.cwordpress

extern int top;
int is_empty(void)
{
return top == -1;
}

stack.h函數

#ifdef STACK_H
#define STACK_H
extern void push(char);
extern char pop(void);
extern int is_empty(void);
#endif

main.c學習

#include
  
  
  
  
#include "stack.h"
int main(void)
{
push('a');
push('b');
push('c');
while(!is_empty())
putchar(pop());
putchar('\n');
return 0;
}

整個程序放在/root/ctest/stack下,文件結構以下:索引

[root@localhost stack]pwd
/root/ctest/stack
[root@localhost stack]tree
.
|-- main.c
`-- stack
|-- empty.c
|-- pop.c
|-- push.c
|-- stack.c
|-- stack.h接口

我將除了main.c外的其餘文件放入stack目錄中,並準備將stack目錄下的全部東西打包爲庫文件,也就是將幾個函數打包。ip

靜態庫

進入stack目錄編譯並生成libstack.a文件,再編譯main.c,生成可執行文件main:

[root@localhost]pwd
/root/ctest/stack/stack
[root@localhost]gcc -c empty.c pop.c push.c stack.c
[root@localhost]ar rs libstack.a empty.o pop.o push.o stack.o
ar:creating libstack.a
[root@localhost]cd ..
[root@localhost]gcc main.c -Lstack -lstack -Istack -omain
[root@localhost]./main
cba

最後的文件結構就變成了:

[root@localhost stack]pwd
/root/ctest/stack
[root@localhost stack]tree
.
|-- main
|-- main.c
`-- stack
|-- empty.c
|-- empty.o
|-- libstack.a
|-- pop.c
|-- pop.o
|-- push.c
|-- push.o
|-- stack.c
|-- stack.h
|-- stack.o

庫文件名都是以lib開頭的,靜態庫以.a做爲後綴,表示Archive。ar命令相似於tar命令,起一個打包的做用,可是把目標文件打包成靜態庫只能用ar命令而不能用tar命令。選項r表示將後面的文件列表添加到文件包,若是文件包不存在就建立它,若是文件包中已有同名文件就替換成新的。s是專用於生成靜態庫的,表示爲靜態庫建立索引,這個索引被連接器使用。
gcc的-L參數指定了庫文件的路徑,-Lstack就是當前目錄下的stack目錄了,若是庫文件在當前目錄,用-L.就好;-lstack指定庫文件名,正如剛纔所說的庫文件都是以lib開頭,因此這裏的參數庫名不要加lib,也不要擴展名.a或.so(動態庫),-Istack,若是你仔細看程序了會發現main.c裏的#include "stack.h",stack.h在stack目錄裏,因此-Istack就指定了頭文件的位置,固然若是這樣寫#include "stack/stack.h"的話,就不須要-Istack了。
另外,gcc使用-print-search-dirs參數能夠查看你的gcc會在哪些目錄裏去查找庫文件,除非你將剛纔的libstack.a放在了這些位置,不然-L參數不可少。

動態庫

動態庫的區別與靜態庫,簡單的說程序在運行時纔會去查找庫文件,而不是像靜態庫同樣在連接時就將整個庫鏈接到了可執行文件中,因此通常來講,使用動態庫的可執行文件要比使用靜態庫的體積小。
刪除剛纔產生的庫文件,目標文件,可執行文件,只留下源碼,執行下邊的命令從新生成動態庫及可執行文件:

[root@localhost]pwd
/root/ctest/stack
[root@localhost]cd stack
[root@localhost]gcc -c -fPIC *.c
[root@localhost]gcc -shared -Wl,-soname,libstack.so.1 -o libstack.so.1.0 *.o

這樣,名爲 libstack.so.1.0的動態庫已經生成,可是連接是gcc只認識名爲libstack.so的庫文件,爲何要加版本號呢,還有-Wl參數指定了libstack.so.1,這又是什麼?咱們在系統的庫文件目錄裏會看到不少的符號連接,這樣作有什麼意義?稍後解釋。

如今須要建立一個符號連接:

[root@localhost]ln -s libstack.so.1.0 libstack.so

再編譯main.c,生成可執行文件:

[root@localhost]cd ..
[root@localhost]gcc main.c -Lstack -lstack -Istack -o main

若是沒有建立符號連接,在編譯main.c的時候就會收到 /usr/bin/ld:can not find -lstack錯誤,連接器找不到stack庫文件。
這個時候執行main會發生一個錯誤error while loading shared libraries: libstack.so: cannot open shared object file: No such file or directory。動態庫沒有找到。
生成的動態庫在/root/ctest/stack/stack目錄下,咱們在編譯連接時指定的庫的位置,可是運行時須要這個庫文件,系統並不會找到/root/ctest/stack/stack裏去。
用ldd命令就能夠看到缺乏了哪些庫:

[root@localhost]ldd main
linux-gate.so.1 => (0x00a4d000)
libstack.so => not found
libc.so.6 => /lib/libc.so.6 (0x00563000)
/lib/ld-linux.so.2 (0x00546000)

經過查看ld.so(8)的Man Page能夠找到幾種解決方法,這裏使用其中一種。
修改/etc/ld.so.conf,其中加上庫文件的路徑/root/ctest/stack/stack/,執行ldconfig生成cache文件/etc/ld.so.cache:

[root@localhost]echo /root/ctest/stack/stack >> /etc/ld.so.conf
[root@localhost]ldconfig

這時libstack.so.1就產生了,標準的libstack.so其實應該連接到這個文件上來。
此時的main即可以執行:

[root@localhost]./main
cba

如今的文件結構應該是:

[root@localhost stack]pwd
/root/ctest/stack
[root@localhost stack]tree
.
|-- main
|-- main.c
`-- stack
|-- empty.c
|-- empty.o
|-- libstack.so -> libstack.so.1.0
|-- libstack.so.1 -> libstack.so.1.0
|-- libstack.so.1.0
|-- pop.c
|-- pop.o
|-- push.c
|-- push.o
|-- stack.c
|-- stack.h
|-- stack.o

能夠將libstack.so刪除,從新建立爲指向libstack.so.1的連接,這是標準的作法。

[參考資料]共享庫的命名慣例

按照共享庫的命名慣例,每一個共享庫有三個文件名:real name、soname和linker name。真正的庫文件(而不是符號連接)的名字是real name,包含完整的共享庫版本號。例如上面的libcap.so.1.十、libc-2.8.90.so等。

soname是一個符號連接的名字,只包含共享庫的主版本號,主版本號一致便可保證庫函數的接口一致,所以應用程序的.dynamic段只記錄共享庫的soname,只要soname一致,這個共享庫就能夠用。例如上面的libcap.so.1和libcap.so.2是兩個主版本號不一樣的libcap,有些應用程序依賴於libcap.so.1,有些應用程序依賴於libcap.so.2,但對於依賴libcap.so.1的應用程序來講,真正的庫文件不論是libcap.so.1.10仍是libcap.so.1.11均可以用,因此使用共享庫能夠很方便地升級庫文件而不須要從新編譯應用程序,這是靜態庫所沒有的優勢。注意libc的版本編號有一點特殊,libc-2.8.90.so的主版本號是6而不是2或2.8。

linker name僅在編譯連接時使用,gcc的-L選項應該指定linker name所在的目錄。有的linker name是庫文件的一個符號連接,有的linker name是一段連接腳本。例如上面的libc.so就是一個linker name,它是一段連接腳本.

$ cat /usr/lib/libc.so
/* GNU ld script
Use the shared library, but some functions are only in
the static library, so try that secondarily. */
OUTPUT_FORMAT(elf32-i386)
GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux.so.2 ) )

從新編譯咱們的libstack,指定它的soname:

$ gcc -shared -Wl,-soname,libstack.so.1 -o libstack.so.1.0 stack.o push.o pop.o is_empty.o

這樣編譯生成的庫文件是libstack.so.1.0,是real name,但這個庫文件中記錄了它的soname是libstack.so.1:

$ readelf -a libstack.so.1.0
......
Dynamic section at offset 0xf10 contains 22 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000e (SONAME) Library soname: [libstack.so.1]
......

若是把libstack.so.1.0所在的目錄加入/etc/ld.so.conf中,而後運行ldconfig命令,ldconfig會自動建立一個soname的符號連接:

$ sudo ldconfig
$ ls -l libstack*
lrwxrwxrwx 1 root root 15 2009-01-21 17:52 libstack.so.1 -> libstack.so.1.0
-rwxr-xr-x 1 djkings djkings 10142 2009-01-21 17:49 libstack.so.1.0但這樣編譯連接main.c卻會報錯:

$ gcc main.c -L. -lstack -Istack -o main
/usr/bin/ld: cannot find -lstack

collect2: ld returned 1 exit status注意,要作這個實驗,你得把先前編譯的libstack共享庫、靜態庫都刪掉,若是先前拷到/lib或者/usr/lib下了也刪掉,只留下libstack.so.1.0和libstack.so.1,這樣你會發現編譯器不認這兩個名字,由於編譯器只認linker name。能夠先建立一個linker name的符號連接,而後再編譯就沒問題了:

$ ln -s libstack.so.1.0 libstack.so $ gcc main.c -L. -lstack -Istack -o main

相關文章
相關標籤/搜索