在linux系統中,可將多個目標文件打包成庫文件,以便在編程時隨時調用,而不必重新編寫或定義,這種包稱為庫函數。庫文件是一些預先編譯好的函數的集合,那些函數都是按照可再使用的原則編寫的。它們通常由一組互相關聯的用來完成某項常見工作的函數構成。比如用來處理屏幕顯示情況的函數(curses庫)等。
1.基礎知識:
linux下GCC在編譯程序時要經歷預處理,編譯,匯編和連接四個階段。
(1). 預處理階段,主要處理#include和#define,它把#include包含進來的.h 文件插入到#include所在的位置,生成.i文件;
(2). 編譯階段,是最重要的階段,在這個階段GCC首先檢查語法然后把文件轉換成匯編程序,生成.s文件;上面這兩步的輸出文件都是文本文件,我們可以用諸如cat的文本處理等命令閱讀這些輸出文件;
(3). 匯編階段,把*.s文件翻譯成二進制機器指令文件*.o,需要反匯編工具如GDB的幫助才能讀懂它;
(4). 連接階段,gcc在這個階段把所有的*.o文件連接成一個可執行文件,庫文件的連接也在這步完成。
2. 標准庫與非標准庫
庫函數可分為標准與非標准(自定義)庫兩大類。
(1) 標准庫文件是公用的,系統中的任何用戶都可以利用這些庫函數,一般保存在/lib或者/usr/lib目錄里,並以頭文件的方式提供包含調用。編譯時要告訴C語言編譯器(更確切地說是鏈接程序)應去查找哪些庫文件,默認情況下,它只會查找標准庫文件。
標准庫在使用時,gcc等編譯程序能夠自動連接,所以在只需要包含其定義的頭文件即可,如libc.a。libc.a為標准C函數庫,它包含了諸如內存管理或者輸入輸出操作的基本函數。
(2) 非標准庫在連接時,必須加上-lname(name為去掉lib和尾部的.a或.so后的庫名)參數;非標准庫可以放在任意目錄中,一般放於當前目錄中,但當放置在非系統默認搜索路徑中時,需要用-Ldir(dir為路徑名稱)指定搜索路徑。
例如,數學函數並不是C標准庫的組成部分,他們是由數學庫/usr/lib/libm.a所定義的,因而在使用該庫中的數學函數時,除了用#include<math.h>將頭文件/usr/include/math.h加入到程序文件中,還要明確的用gcc的-lm選項來連接這個庫(數學庫libm.a放置在系統默認的庫搜索路徑/usr/lib中,因而不需要-Ldir參數)。
3. 靜態庫與共享庫
函數庫一般分為靜態和共享(也稱動態庫)兩種格式,庫文件必須遵守一定的命名規則,庫文件的名字永遠以lib打頭,隨后是說明函數庫情況的部分(比如用c表示這是一個 C語言庫,而m表示這是一個數學運算庫等)。文件名的最后部分以一個句點(.)開始,然后給出這個庫文件的類型,.a為靜態庫文件,.so和.sa為共享型庫文件。無論動態庫還是靜態庫都需要用.o文件來生成。
一般 Linux 系統把 /lib 和 /usr/lib 兩個目錄作為默認的庫(動態庫或靜態庫)搜索路徑,所以使用這兩個目錄中的庫時不需要進行設置搜索路徑即可直接使用。對於處於默認庫搜索路徑之外的庫,需要將庫的位置添加到庫的搜索路徑之中(如修改環境變量),也可以在程序連接時,通過-L參數來指定。
但由於在運行時,程序還需要連接動態庫,因而,對於動態庫,最好還是將庫的位置添加到庫的搜索路徑之中或是把所用的庫拷貝到系統默認的庫搜索目錄中。靜態庫則無此限制。
(1)靜態庫
靜態庫也叫做檔案(archive),文件名按慣例都以.a結尾,比如C語言標准庫/usr/lib/libc.a,X11庫/usr/X11R6/lib/libX11.a等。它所包含的成員是若干.o文件,靜態庫中的各個成員(.o文件)沒有特殊的存在格式,僅僅是一個.o文件的集合。在連接時,靜態庫的文件代碼會被拷貝到可執行文件中,所以靜態連接的可執行文件一般比較大一些。
在Linux(Unix)中使用工具“ar”對靜態庫進行維護管理,創建靜態庫用ar命令,在系統提示符下鍵入以下命令將創建靜態庫文件libmyhello.a。
# ar -rc libmyhello.a hello.o
靜態庫的缺點是,如果我們在同一時間運行多個程序而它們又都使用着來自同一個函數庫里的函數時,內存里就會有許多份同一函數的備份,在程序文件本身也有許多份同樣的備份。這會消耗大量寶貴的內存和硬盤空間。
(2)共享庫
也稱動態庫,許多UNIX系統支持共享庫,它同時克服了靜態庫在內存和硬盤方面的無謂消耗。鏈接時,動態庫的代碼不會被加入可執行文件中,而是在程序被執行的時候加載。
共享庫的默認存放位置和靜態庫是一樣的,但有着不同的文件后綴。在一個典型的 Linux系統上,C語言標准庫的共享版本是/usr/lib/libc.so N,其中的N是主版本號。當靜態庫和動態庫同名時,gcc命令將優先使用動態庫。
可用gcc來創建動態庫,在系統提示符下鍵入以下命令得到動態庫文件libmyhello.so。
# gcc -shared -fPCI -o libmyhello.so hello.o
4. 對比
靜態庫在程序編譯時會被連接到目標代碼中,程序運行時將不再需要該靜態庫。動態庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行時才被載入,因此在程序運行時還需要動態庫存在。
假如程式是在編譯時加載庫文檔的,就是使用了靜態庫。假如是在運行時加載目標代碼,就成為動態庫。換句話說,假如是使用靜態庫,則靜態庫代碼在編譯時就拷貝到了程式的代碼段,程式的體積會膨脹。假如使用動態庫,則程式中只保留庫文檔的名字和函數名,在運行時去查找庫文檔和函數體,程式的體積基本變化不大。
靜態庫的原則是“以空間換時間”,增加程式體積,減少運行時間;動態庫則是“以時間換空間”,增加了運行時間,但減少了程式本身的體積。
5. 總結
標准靜態庫或共享庫,路徑和庫名都不需顯示指定;
非標准靜態庫,必須顯示指定庫名,不在默認路徑中時需指定路徑;
非標准共享庫,編譯時:必須指定庫名,不在默認路徑中需指定路徑名,
運行時,不在默認路徑中時需要將庫位置添加到庫搜索路徑中或拷貝庫到默認路徑中。
6. gcc相關命令
使用要點:
⑴在gcc 的-I參數后加上庫頭文件的路徑;
⑵在gcc 的-L參數后加上庫文件所在目錄;
⑶在gcc 的-l參數后加上庫文件名,但是要去掉lib和.a擴展名,比如庫文件名是libtest.a 那么參數就是-ltest。
2. linux中的內存函數
(1). getpagesize(獲得內存分頁大小)
#include <unistd.h>
size_t getpagesize(void);
返回分頁的大小,單位為字節。此為系統分頁大小,不一定和硬件分頁大小相同。
(2). mmap(建立內存映射)
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
將某個文件內容映射到內存中, 對該內存區域的存取即是直接對該文件內容的讀寫
(3). munmap(解除內存映射)
3. 時間函數
(4). ftime(獲得目前的時間和日期)
#include <sys/timeb.h>
int ftime(struct timeb*tp);
將目前時間日期由tp所指的結構返回。tp結構定義為:
struct timeb{
time_t time;//從1970年1月1日至今的秒數
unsigned short millitm;// 毫秒
short timezone;
short dstflag;
};
(5). gettimeofday(獲得目前的時間)
#include <sys/time.h>
#include <unistd.h>
int gettimeofday(struct timeval*tv,struct timezone *tz);
settimeofday(設置目前時間)
4.
(6). ffs(在一整型數中查找第一個值為真的位)
#include <string.h>
int ffs(int i);
ffs會由低位至高位,判斷參數i字節中的每一位,將最先出現位的值為1的位置返回。
5. 字符串函數
(7). #include <strings.h>
char *index(const char *s, int c);
char *rindex(const char *s, int c);
查找字符c在字符串s中出現的位置。
與strchr類似
(8). strcasecmp(忽略大小寫比較字符串)
#include <strings.h>
int strcasecmp(const char *s1, const char *s2);
(9). strfry 隨機重組字符串內容的字符串
#define _GNU_SOURCE
#include <string.h>
char *strfry(char *string);
由於strfry會改變參數string字符串內容,所以參數string必須是指向可以寫入的內存地址。
int strncasecmp(const char *s1, const char *s2, size_t n);
6. 加密函數
(10). crypt 將密碼或數據加密
#define _XOPEN_SOURCE
#include <unistd.h>
char *crypt(const char *key, const char *salt);
使用gcc編譯時需要加入-lcrypt
(11). getpass 取得密碼輸入
#include <unistd.h>
char *getpass( const char *prompt);
7.數據結構函數
(12). #include <stdlib.h>
void *bsearch(const void *key, const void *base,
size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
8. 進程間通信函數
(13).mkfifio 建立具名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
(14). pipe 建立管道
#include <unistd.h>
int pipe(int fields[2]);
pipe()建立管道,並將文件描述詞有參數fields數組返回。filedes[0]為管道讀取端,filedes[1]是管道寫入端
(15). popen 建立管道I/O
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
函數傳入值:Command:指向的是一個以null結束符結尾的字符串,這個字符串包含一個shell命令,並被送到/bin/sh以-c參數執行,即由shell來執行type:“r”:文件指針鏈接到commond的標准輸出,即該命令的結果產生輸出 “w”:文件指針連接到command的標准輸入,即該命令的結果產生輸入
函數返回值:成功:文件流指針 出錯:-1
(16). connect 建立socket連線
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);