Linux下的靜態庫和動態庫

一、首先,先引入爲什麼要有庫,看下面的程序,也就是我們和一般寫的程序差不多

//結構樹
tt
├── a.c
├── a.h
├── b.c
├── b.h
├── c.c
├── c.h
└── main.c
//a.h
#ifndef A_H
#define A_H
void a();
#endif
//a.c
#include "a.h"
#include "b.h"//其實這裏不include的話,你直接編譯這個子模塊的話是沒有錯的,但是在鏈接的過程中就可能出錯了,因爲鏈接的時候是唯一確定符號的,而且不寫的話,它就是一個int b();的函數了,但是與你定義的格式可能不一新,你定義的可能就是char *b(int i,int *p);
void a(){
    b();
}
//b.h
#ifndef B_H
#define B_H
void b();
#endif
//b.c
#include "b.h"
#include "c.h"
void b(){
	c();
}
//c.h
#ifndef C_H
#define C_H
void c();
#endif
//c.c
#include <stdio.h>
#include "c.h"
void c(){
	printf("hello world !\n");
}
//main.c
#include "a.h"
#include <stdio.h>
int main(){
	a();
}
[root@localhost tt]# ls
a.c  a.h  b.c  b.h  c.c  c.h  main.c
[root@localhost tt]# gcc a.c b.c c.c main.c -o main   //這裏用到什麼*.c就是編譯啦,筆者在這裏犯一個錯誤,找了好久,最後才發現沒有加上子功能函數~~
[root@localhost tt]# ls
a.c  a.h  b.c  b.h  c.c  c.h  main  main.c
[root@localhost tt]# ./main 
hello world !
[root@localhost tt]# 
/*
1.一般我們寫的程序都很小的,而且都是放在一個目錄下面的,但是這樣當項目大了就不好管理了而且當源程序太多了的話就會顯得太亂了;
2.講一下一般的C語言項目的規範吧,一般不同的人會寫不同的程序來實現不同的模塊和功能,也就是我們寫的*.c,
3.但是就別人要調用我們的功能的時候怎麼辦呢,所以我們就把函數的功能和聲明放在一個頭文件裏,裏面寫明瞭這個函數的功能和參數和返回值的意義,這就是*.h;
4.所以別人要用我們寫的功能函數時候,我們直接把源程序編譯好成*.o和頭文件一起發過去即可(或者你會問爲什麼不發一個源代碼呢,因爲對方不一定和你在同一個地方,而且這也很可以),也就是說一般是一個xx.c對應一個xx.h,但是一般的啦,
5.然後項目負責人寫一個main.c調用這樣寫好的*.c和include*.h即可,用到哪些就調用哪些和include哪些就好了,或者再寫一個makefile來編譯這些函數即可,又可以直接編譯,例如上面的gcc a.c b.c c.c main.c -o main;
6.但是有個嚴重的問題,如果太多了功能子函數了,這樣頭文件也足夠多了,假如有100個,1000個,那麼gcc 後面寫的話會寫死人的,而且不能寫錯,所以就產生了庫,用來方便管理這些子功能函數,同時會把打包到庫的子功能函數的聲明放在一個總的頭文件裏面,
7.而且項目的話一般會有不同的目錄來管理不同的文件,一般至少有下面說到的三個文件夾,include,
lib,src--include一般就會放頭文件,也就是全部子功能函數的說明都在放在裏面,lib就是用來存放庫的,src就是子功能函數的源代碼和主函數,makefile一般放在與這些子目錄的同一目錄下面
8.無論是靜態庫還是動態庫都是由*.o組成的,下面就介紹庫吧!~
*/

二、庫分爲靜態庫和動態庫

靜態庫名字格式:靜態庫一般以lib爲前綴,.a爲後綴,中間纔是庫的名字,eg:libm.a,libxx.a

優點:使用靜態庫函數編譯成的文件比較大,因爲整個函數庫的所有數據都會被整合進目標代碼中。編譯後的執行程序就不需要外部的函數庫支持,直接運行即可

缺點:靜態函數庫一旦改變了,那麼程序就必須重新編譯,而且編譯出來較大

編譯庫:先把所有的*.c即子功能函數編譯成*.o,然後用ar歸檔,最後直接使用即可,看下面的例子:

libtest/
├── include
│   └── abc.h
├── lib
│   ├── a.o
│   ├── b.o
│   └── c.o
└── src
    ├── a.c
    ├── b.c
    ├── c.c
    └── main.c
//只修改兩個部分,main.c和abc.h,其它的放在相應的目錄即可,不用修改,lib裏面的內容
gcc -c a.c -o  a.o;gcc -c b.c -o b.o;gcc -c c.c -o c.o生成即可
//include/abc.h-->把所有的子功能函數的聲明和功能的說明放在這個文件中
#ifndef ABC_H
#define ABC_H

void a();
void b();
void c();

#endif
//main.c
#include "abc.h"
#include <stdio.h>
int main(){
	a();
}
[root@localhost lib]# pwd
/home/yyj/libtest/lib
[root@localhost lib]# ls
a.o  b.o  c.o
[root@localhost lib]# ar -rcs libmy_lib.a a.o b.o c.o 
[root@localhost lib]# ar -rcs libmy_lib.o a.o b.o c.o 
[root@localhost lib]# ar -rcs libmy_lib.xo a.o b.o c.o 
[root@localhost lib]# ar -rcs libmy_lib.xo.a a.o b.o c.o 
//rc命令來打包,c就是創建一個新的歸檔文件,r就是在這個歸檔文件中插入文件,s就是在歸檔文件中創建這些文件的索引
[root@localhost lib]# ls
a.o  b.o  c.o  libmy_lib.a  libmy_lib.o  libmy_lib.xo  libmy_lib.xo.a
[root@localhost lib]# rm a.o b.o c.o -f //已經製作成庫了,*.o可以不要了
[root@localhost lib]# cd ../src;rm a.c b.c c.c -f //*.c也可以不要了,main.c不能刪除
[root@localhost src]# pwd
/home/yyj/libtest/src
[root@localhost src]# ls
main.c
[root@localhost src]# gcc main.c -I../include/ -L../lib -lmy_lib -o main
[root@localhost src]# ./main 
hello world ![root@localhost src]# 
[root@localhost src]# ll
總用量 12
-rwxr-xr-x. 1 root root 4730 12月 29 20:06 main//因爲把整個庫都放都進來了,所以會比較大
-rw-r--r--. 1 root root   56 12月 29 19:46 main.c
//下面的實驗就是證明:
    1)鏈接靜態的時候不能再使用-static,因爲-static就是靜態鏈接的意思
    2)靜態庫的命名必須合符規範的,不然會錯,前綴爲lib,後綴爲.a,不然會提示找不到
[root@localhost src]# gcc -static main.c -I../include -L../lib -lmy_lib -o main1/usr/bin/ld: cannot find -lc
collect2: ld 返回 1
//解釋一下-I指定頭文件 -L指定庫的路徑-l指定鏈接哪些庫,需要去掉庫前綴和後綴,因爲頭文件和庫都不在Linux的默認路徑下,詳細查看博客的《Linux頭文件與庫的搜索路徑》
[root@localhost src]# gcc main.c -I../include -L../lib -lmy_lib.xo -o main1
[root@localhost src]# ls
main  main1  main.c
[root@localhost src]# ./main1
[root@localhost src]# LD_LIBRARY_PATH=../lib/
[root@localhost src]# gcc main.c -lmy_lib -I../include -o main4
[root@localhost src]# ./main4
hello world ![root@localhost src]# 
/************************注意:無論是動態鏈接還是靜態鏈接都是指是包括程序員自己本身要鏈接的庫和系統的庫********************************************/
//ldd就是用來查看當前的執行文件鏈接了哪些動態庫的,最後一個/lib/ld-linux.so.2是動態加載器(是一個可執行程序),後面是是鏈接地址!
hello world ![root@localhost src]# ldd main
	linux-gate.so.1 =>  (0x00c7e000)
	libc.so.6 => /lib/libc.so.6 (0x00209000)
	/lib/ld-linux.so.2 (0x00bec000)
[root@localhost src]# ldd main1
	linux-gate.so.1 =>  (0x00bc8000)
	libc.so.6 => /lib/libc.so.6 (0x00c12000)
	/lib/ld-linux.so.2 (0x00bec000)
[root@localhost src]# ldd /bin/ln
	linux-gate.so.1 =>  (0x00ca2000)
	libc.so.6 => /lib/libc.so.6 (0x001ea000)
	/lib/ld-linux.so.2 (0x00bec000)
[root@localhost src]# 
/*動態庫*/

三、動態庫名字格式:動態庫一般以lib爲前綴,.so爲後綴,中間纔是庫的名字,eg:libm.so,libxx.so

優點:使用動態庫函數編譯成的文件比較小,而且是在程序運行的時候才加載到內存裏,可以與其它程序共享,以此來節省內存,

用別人的話就是:某個程序的在運行中要調用某個動態鏈接庫函數的時候,操作系統首先會查看所有正在運行的程序,看在內存裏是否已有此庫函數的拷貝了。

如果有,則讓其共享那一個拷貝;只有沒有才鏈接載入。這樣的模式雖然會帶來一些“動態鏈接”額外的開銷,卻大大的節省了系統的內存資源。

C的標準庫就是動態鏈接庫,也就是說系統中所有運行的程序共享着同一個C標準庫的代碼段。

程序或者是軟件升級比較方便,直接升級動態庫即可,不用重新編譯和鏈接其它庫和代碼即可

缺點:平臺性不好


製作:先把子功能函數都編譯成*.o,但是需要添加參數-fPIC,-f後面接選項,PIC(position independent code與鏈接地址獨立的代碼),然後使用gcc -shared鏈接選項生成動態庫

我們還是以上面的爲例子吧,看下面的操作

libtest/
├── include
│   ├── a.h
│   ├── b.h
│   └── c.h
├── lib
│   └── libmy_lib.a//這個我們上面製造的靜態庫,暫時放着
└── src
    ├── a.c
    ├── b.c
    ├── c.c
    └── main.c
[root@localhost lib]# pwd
/root/桌面/library/libtest/lib
//編譯生成與鏈接地址無關的二進制文件
[root@localhost lib]# gcc -c -fPIC -I../include ../src/a.c -o a.o
[root@localhost lib]# gcc -c -fPIC -I../include ../src/b.c -o b.o
[root@localhost lib]# gcc -c -fPIC -I../include ../src/c.c -o c.o
[root@localhost lib]# ls
a.o  b.o  c.o  libmy_lib.a
//編譯生成
[root@localhost src]# gcc main.c -I../include -L../include -lmySlib -o main
[root@localhost src]# ls
a.c  b.c  c.c  main  main.c
[root@localhost src]# ./main
./main: error while loading shared libraries: libmySlib.so: cannot open shared object file: No such file or directory
[root@localhost src]# cd ../include/
[root@localhost include]# LD_LIBRARY_PATH=`pwd`
[root@localhost include]# ../src/main
hello world
[root@localhost include]#


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章