通過編譯函數庫來學習GCC【轉】
轉自:http://blog.csdn.net/u012365926/article/details/51446295
基本概念
什麽是庫
在windows平臺和linux平臺下都大量存在著庫。
本質上來說庫是一種可執行代碼的二進制形式,可以被操作系統載入內存執行。
由於windows和linux的平臺不同(主要是編譯器、匯編器和連接器的不同),因此二者庫的二進制是不兼容的。
本文僅限於介紹linux下的庫。
庫的種類
linux下的庫有兩種:靜態庫和共享庫(動態庫)。
二者的不同點在於代碼被載入的時刻不同。
靜態庫的代碼在編譯過程中已經被載入可執行程序,因此體積較大。
共享庫的代碼是在可執行程序運行時才載入內存的,在編譯過程中僅簡單的引用,因此代碼體積較小。
庫存在的意義
庫是別人寫好的現有的,成熟的,可以復用的代碼,你可以使用但要記得遵守許可協議。
現實中每個程序都要依賴很多基礎的底層庫,不可能每個人的代碼都從零開始,因此庫的存在意義非同尋常。
共享庫的好處是,不同的應用程序如果調用相同的庫,那麽在內存裏只需要有一份該共享庫的實例。
庫文件是如何產生的在linux下
靜態庫的後綴是.a,它的產生分兩步
Step 1.由源文件編譯生成一堆.o,每個.o裏都包含這個編譯單元的符號表
Step 2.ar命令將很多.o轉換成.a,成為靜態庫
動態庫的後綴是.so,它由gcc加特定參數編譯產生。
具體方法參見後文實例。
庫文件是如何命名的,有沒有什麽規範
在linux下,庫文件一般放在/usr/lib和/lib下,
靜態庫的名字一般為libxxxx.a,其中xxxx是該lib的名稱
動態庫的名字一般為libxxxx.so.major.minor,xxxx是該lib的名稱,major是主版本號, minor是副版本號
如何知道一個可執行程序依賴哪些庫
ldd命令可以查看一個可執行程序依賴的共享庫,
例如# ldd /bin/lnlibc.so.6
=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2
=> /lib/ld- linux.so.2 (0×40000000)
在新安裝一個庫之後如何讓系統能夠找到他
如果安裝在/lib或者/usr/lib下,那麽ld默認能夠找到,無需其他操作。
如果安裝在其他目錄,需要將其添加到/etc/ld.so.cache文件中,步驟如下
1.編輯/etc/ld.so.conf文件,加入庫文件所在目錄的路徑
2.運行ldconfig,該命令會重建/etc/ld.so.cache文件
用gcc生成靜態和動態連接庫的示例
我們通常把一些公用函數制作成函數庫,供其它程序使用。
函數庫分為靜態庫和動態庫兩種。
靜態庫在程序編譯時會被連接到目標代碼中,程序運行時將不再需要該靜態庫。
動態庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入,因此在程序運行時還需要動態庫存在。
本文主要通過舉例來說明在Linux中如何創建靜態庫和動態庫,以及使用它們。
為了便於闡述,我們先做一部分準備工作。
準備好測試代碼hello.h、hello.c和main.c;
hello.h(見程序1)為該函數庫的頭文件。
hello.c(見程序2)是函數庫的源程序,其中包含公用函數hello,該函數將在屏幕上輸出”Hello XXX!”。
main.c(見程序3)為測試庫文件的主程序,在主程序中調用了公用函數hello。
程序1: hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif
- 1
- 2
- 3
- 4
程序2:hello.c
#include <stdio.h>
void hello(const char *name) {
printf(“Hello %s!\n”, name);
}
- 1
- 2
- 3
- 4
- 5
程序3:main.c
#include “hello.h”
int main()
{
hello(“everyone”);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
問題的提出
註意:這個時候,我們編譯好的hello.o是無法通過gcc –o 編譯的,這個道理非常簡單,
hello.c是一個沒有main函數的.c程序,因此不夠成一個完整的程序,如果使用gcc –o 編譯並連接它,GCC將報錯。
編譯main.c也會出錯。因為找不到hello的函數原型及實現。
無論靜態庫,還是動態庫,都是由.o文件創建的。因此,我們必須將源程序hello.c通過gcc先編譯成.o文件。
這個時候我們有三種思路:
- 通過編譯多個源文件,直接將目標代碼合成一個.o文件。
- 通過創建靜態連接庫libmyhello.a,使得main函數調用hello函數時可調用靜態連接庫.
- 通過創建動態連接庫libmyhello.so,使得main函數調用hello函數時可調用靜態連接庫。
思路一:編譯多個源文件
在系統提示符下鍵入以下命令得到hello.o文件。
$ gcc -c hello.c
- 1
為什麽不使用gcc–o hello hello.cpp 這個道理我們之前已經說了,使用-c是什麽意思呢?這涉及到gcc 編譯選項的常識。
我們通常使用的gcc –o 是將.c源文件編譯成為一個可執行的二進制代碼(-o選項其實是制定輸出文件文件名,如果不加-c選項,gcc默認會編譯連接生成可執行文件,文件的名稱有-o選項指定),這包括調用作為GCC內的一部分真正的C編譯器(ccl),以及調用GNU C編譯器的輸出中實際可執行代碼的外部GNU匯編器(as)和連接器工具(ld)。
而gcc
–c是使用GNU匯編器將源文件轉化為目標代碼之後就結束,在這種情況下,只調用了C編譯器(ccl)和匯編器(as),而連接器(ld)並沒有被執行,所以輸出的目標文件不會包含作為Linux程序在被裝載和執行時所必須的包含信息,但它可以在以後被連接到一個程序。
我們運行ls命令看看是否生成了hello.o文件。
$ ls
hello.c hello.h hello.o main.c
- 1
- 2
在ls命令結果中,我們看到了hello.o文件,本步操作完成。
同理編譯main
$ gcc –c main.c
- 1
將兩個文件連接成一個.o文件。
$ gcc –o hello hello.o main.o
- 1
運行
$ ./hello
Hello everyone!
- 1
- 2
完成^ ^!
思路二:靜態連接庫
下面我們先來看看如何創建靜態庫,以及使用它。
靜態庫文件名的命名規範是以lib為前綴,緊接著跟靜態庫名,擴展名為.a。例如:我們將創建的靜態庫名為myhello,則靜態庫文件名就是libmyhello.a。在創建和使用靜態庫時,需要註意這點。創建靜態庫用ar命令。
在系統提示符下鍵入以下命令將創建靜態庫文件libmyhello.a。
$ ar rcs libmyhello.a hello.o
- 1
我們同樣運行ls命令查看結果:
$ ls
hello.c hello.h hello.o libmyhello.a main.c
- 1
- 2
- 3
ls命令結果中有libmyhello.a。
靜態庫制作完了,如何使用它內部的函數呢?
只需要在使用到這些公用函數的源程序中包含這些公用函數的原型聲明,然後在用gcc命令生成目標文件時指明靜態庫名,gcc將會從靜態庫中將公用函數連接到目標文件中。註意,gcc會在靜態庫名前加上前綴lib,然後追加擴展名.a得到的靜態庫文件名來查找靜態庫文件,因此,我們在寫需要連接的庫時,只寫名字就可以,如libmyhello.a的庫,只寫-lmyhello。也可以直接在調用靜態庫的全名即
libmyhello.a
在程序3:main.c中,我們包含了靜態庫的頭文件hello.h,然後在主程序main中直接調用公用函數hello。下面先生成目標程序hello,然後運行hello程序看看結果如何。
方式1:$ gcc -o hello main.c -static -L ./ -lmyhello
方式2:$ gcc -o hello main.c -static -L ./ -libmyhello.a
- 1
- 2
兩種方式都可以生成已連接靜態庫的hello程序
$ ./hello
Hello everyone!
- 1
- 2
- 3
我們刪除靜態庫文件試試公用函數hello是否真的連接到目標文件 hello中了。
$ rm libmyhello.a
rm: remove regular file `libmyhello.a’? y
$ ./hello
Hello everyone!
- 1
- 2
- 3
- 4
程序照常運行,靜態庫中的公用函數已經連接到目標文件中了。
靜態連接庫的一個缺點是,如果我們同時運行了許多程序,並且它們使用了同一個庫函數,這樣,在內存中會大量拷貝同一庫函數。這樣,就會浪費很多珍貴的內存和存儲空間。使用了共享連接庫的Linux就可以避免這個問題。
共享函數庫和靜態函數在同一個地方,只是後綴有所不同。比如,在一個典型的Linux系統,標準的共享數序函數庫是/usr/lib/libm.so。
當一個程序使用共享函數庫時,在連接階段並不把函數代碼連接進來,而只是連接函數的一個引用。當最終的函數導入內存開始真正執行時,函數引用被解析,共享函數庫的代碼才真正導入到內存中。這樣,共享連接庫的函數就可以被許多程序同時共享,並且只需存儲一次就可以了。共享函數庫的另一個優點是,它可以獨立更新,與調用它的函數毫不影響。
思路三、動態連接庫(共享函數庫)
我們繼續看看如何在Linux中創建動態庫。我們還是從.o文件開始。
動態庫文件名命名規範和靜態庫文件名命名規範類似,也是在動態庫名增加前綴lib,但其文件擴展名為.so。例如:我們將創建的動態庫名為myhello,則動態庫文件名就是libmyhello.so。用gcc來創建動態庫。
在系統提示符下鍵入以下命令得到動態庫文件libmyhello.so。
$ gcc -shared -fPIC -o libmyhello.so hello.o
- 1
“PIC”命令行標記告訴GCC產生的代碼不要包含對函數和變量具體內存位置的引用,這是因為現在還無法知道使用該消息代碼的應用程序會將它連接到哪一段內存地址空間。這樣編譯出的hello.o可以被用於建立共享連接庫。建立共享連接庫只需要用GCC的”-shared”標記即可。
與原博客不同的是我在這步出現了錯誤的地方。不能生成libmyhello.so。後來借鑒其他博客的方法是hello.o的問題。我想的解決辦法為:如果要使用生成動態庫,先要使用-fPIC的選項生成hello.o的文件。然後再運行上條命令,即可
$ gcc -c -fPIC -o hello.o hello.c
- 1
我們照樣使用ls命令看看動態庫文件是否生成。
$ ls
hello.cpp hello.h hello.o libmyhello.so main.cpp
- 1
- 2
調用動態連接庫編譯目標文件。
在程序中使用動態庫和使用靜態庫完全一樣,也是在使用到這些公用函數的源程序中包含這些公用函數的原型聲明,然後在用gcc命令生成目標文件時指明動態庫名進行編譯。我們先運行gcc命令生成目標文件,再運行它看看結果。
如果直接用如下方法進行編譯,並連接:
$ gcc -o hello main.c -L ./ -lmyhello
(使用”-lmyhello”標記來告訴GCC驅動程序在連接階段引用共享函數庫libmyhello.so。”-L ./ ”標記告訴GCC函數庫可能位於當前目錄。否則GNU連接器會查找標準系統函數目錄:它先後搜索
- elf文件的 DT_RPATH段
- 環境變量LD_LIBRARY_PATH
- /etc/ld.so.cache文件列表
- /lib/,/usr/lib目錄找到庫文件後將其載入內存,但是我們生成的共享庫在當前文件夾下,並沒有加到上述的4個路徑的任何一個中,因此,執行後會出現錯誤)
$ ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory
- 1
- 2
錯誤提示,找不到動態庫文件libmyhello.so。程序在運行時,會在/usr/lib和/lib等目錄中查找需要的動態庫文件。若找到,則載入動態庫,否則將提示類似上述錯誤而終止程序運行。有多種方法可以解決,
(1) 我們將文件 libmyhello.so復制到目錄/usr/lib中,再試試。
$ mv libmyhello.so /usr/lib
$ ./hello
- 1
- 2
成功!
(2) 既然連接器會搜尋LD_LIBRARY_PATH所指定的目錄,那麽我們可以將這個環境變量設置成當前目錄:
先執行:
$ export LD_LIBRARY_PATH=$(pwd)
- 1
再執行:
$ ./hello
- 1
成功!
執行:
sudo ldconfig /usr/local/lib
- 1
註: 當用戶在某個目錄下面創建或拷貝了一個動態連接庫,若想使其被系統共享,可以執行一下”ldconfig 目錄名“這個命令.此命令的功能在於讓ldconfig將指定目錄下的動態連接庫被系統共享起來,意即:在緩存文件/etc/ld.so.cache中追加進指定目錄下的共享庫.本例讓系統共享了/usr/local/lib目錄下的動態連接庫.該命令會重建/etc/ld.so.cache文件
成功!
這也進一步說明了動態庫在程序運行時是需要的。
可以查看程序執行時調用動態庫的過程:
$ ldd hello
- 1
執行 test,可以看到它是如何調用動態庫中的函數的。
[[email protected] 20090505]$ ldd hello
linux-gate.so.1 => (0x00110000)
libmyhello.so => /usr/lib/libmyhello.so (0x00111000)
libc.so.6 => /lib/libc.so.6 (0x00859000)
/lib/ld-linux.so.2 (0x0083a000)
我們回過頭看看,發現使用靜態庫和使用動態庫編譯成目標程序使用的gcc命令完全一樣,
那當靜態庫和動態庫同名時,gcc命令會使用哪個庫文件呢?抱著對問題必究到底的心情,
來試試看。
先刪除除.c和.h外的所有文件,恢復成我們剛剛編輯完舉例程序狀態。
$ rm -f hello hello.o /usr/lib/libmyhello.so
$ ls
hello.c hello.h main.c
- 1
- 2
- 3
在來創建靜態庫文件libmyhello.a和動態庫文件libmyhello.so。
$ gcc -c hello.c
$ ar rcs libmyhello.a hello.o
$ gcc -c -fPIC -o hello.o hello.c
$ gcc -shared -o libmyhello.so hello.o
$ ls
$ hello.c hello.h hello.o libmyhello.a libmyhello.so main.c
- 1
- 2
- 3
- 4
- 5
- 6
通過上述最後一條ls命令,可以發現靜態庫文件libmyhello.a和動態庫文件libmyhello.so都已經生成,並都在當前目錄中。然後,我們運行gcc命令來使用函數庫myhello生成目標文件hello,並運行程序 hello。
$ gcc -o hello main.c -L ./ -lmyhello
$ ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory
- 1
- 2
- 3
- 4
從程序hello運行的結果中很容易知道,當靜態庫和動態庫同名時, gcc命令將優先使用動態庫。
編譯參數解析
最主要的是GCC命令行的一個選項:
-shared 該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標誌外部程序無法連接。相當於一個可執行文件
- -fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯後的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。
- -L ./:表示要連接的庫在當前目錄中
-
LD_LIBRARY_PATH:這個環境變量指示動態連接器可以裝載動態庫的路徑。
當然如果有root權限的話,可以修改/etc/ld.so.conf文件,然後調用 /sbin/ldconfig來達到同樣的目的,不過如果沒有root權限,那麽只能采用輸出LD_LIBRARY_PATH的方法了。
靜態庫連接時搜索路徑順序:
-
ld會去找GCC命令中的參數-L
-
再找gcc的環境變量LIBRARY_PATH
-
再找內定目錄 /lib, /usr/lib, /usr/local/lib 這是當初compile gcc時寫在程序內的
動態連接時、執行時搜索路徑順序:
-
編譯目標代碼時指定的動態庫搜索路徑;
-
環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑;
-
配置文件/etc/ld.so.conf中指定的動態庫搜索路徑;
-
默認的動態庫搜索路徑/lib;
-
默認的動態庫搜索路徑/usr/lib。
有關環境變量:
LIBRARY_PATH環境變量:指定程序靜態連接庫文件搜索路徑
LD_LIBRARY_PATH環境變量:指定程序動態連接庫文件搜索路徑
Linux下Gcc生成和使用靜態庫和動態庫詳解(轉)
C編譯: 動態連接庫 (.so文件)
若表達有誤,敬請斧正。
編譯動態庫的快捷方式
- 源碼 hello.c,hello.h,main.c
- gcc -c -fPIC -o hello.o hello.c,使用這個命令來生成.o文件
- gcc -shared -fPIC -o libmyhello.so hello.o,使用這個命令來生成lib.so文件
- gcc -o hello main.c -L ./ -lmyhello,使用這個命令來編譯並鏈接生成目標文件
通過編譯函數庫來學習GCC【轉】