Linux 动态库和静态库
From:http://blog.csdn.net/u010977122/article/details/52958330
From:http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520101023104745738/
linux下動態與靜態鏈接庫的使用及區別:http://blog.csdn.net/shreck66/article/details/49583057
Linux下動態庫(.so)和靜態庫(.a):http://blog.csdn.net/felixit0120/article/details/7652907
http://www.latelee.org/programming-under-linux/library-on-linux.html
從源代碼到可執行代碼
? ? ? ? 今天我們主要來說說Linux系統下基于動態庫(.so)和靜態(.a)的程序那些貓膩。在這之前,我們需要了解一下源代碼到可執行程序之間到底發生了什么神奇而美妙的事情。
? ? ? 在linux操作系統中,普遍使用ELF格式作為可執行程序或者程序生成過程中的中間格式。ELF(Executable and Linking Format,可執行連接格式)是UNIX系統實驗室(USL)作為應用程序二進制接口(Application BinaryInterface,ABI)而開發和發布的。工具接口標準委員會(TIS)選擇了正在發展中的ELF標準作為工作在32位Intel體系上不同操作系統之間可移植的二進制文件格式。本文不對ELF文件格式及其組成做太多解釋,以免沖淡本文的主題,大家只要知道這么個概念就行。以后再詳解Linux中的ELF格式。源代碼到可執行程序的轉換時需要經歷如下圖所示的過程:
編譯是指把用高級語言編寫的程序轉換成相應處理器的匯編語言程序的過程。從本質上講,編譯是一個文本轉換的過程。對嵌入式系統而言,一般要把用C語言編寫的程序轉換成處理器的匯編代碼。編譯過程包含了c語言的語法解析和匯編碼的生成兩個步驟。編譯一般是逐個文件進行的,對于每一個C語言編寫的文件,可能還需要進行預處理。
匯編是從匯編語言程序生成目標系統的二進制代碼(機器代碼)的過程。機器代碼的生成和處理器有密切的聯系。相對于編譯過程的語法解析,匯編的過程相對簡單。這是因為對于一款特定的處理器,其匯編語言和二進制的機器代碼是一一對應的。匯編過程的輸入是匯編代碼,這個匯編代碼可能來源于編譯過程的輸出,也可以是直接用匯編語言書寫的程序。
連接是指將匯編生成的多段機器代碼組合成一個可執行程序。一般來說,通過編譯和匯編過程,每一個源文件將生成一個目標文件。連接器的作用就是將這些目標文件組合起來,組合的過程包括了代碼段、數據段等部分的合并,以及添加相應的文件頭。
GCC是Linux下主要的程序生成工具,它除了編譯器、匯編器、連接器外,還包括一些輔助工具。在下面的分析過程中我會教大家這些工具的基本使用方法,Linux的強大之處在于,對于不太懂的命令或函數,有一個很強大的“男人”時刻stand by your side,有什么不會的就去命令行終端輸入:man [命令名或函數名] 。
對于最后編譯出來的可執行程序,當我們執行它的時候,操作系統又是如何反應的呢?我們先從宏觀上來個總體把握,如圖2所示:
作為UNIX操作系統的一種,Linux的操作系統提供了一系列的接口,這些接口被稱為系統調用(System Call)。在UNIX的理念中,系統調用"提供的是機制,而不是策略"。C語言的庫函數通過調用系統調用來實現,庫函數對上層提供了C語言庫文件的接口。在應用程序層,通過調用C語言庫函數和系統調用來實現功能。一般來說,應用程序大多使用C語言庫函數實現其功能,較少使用系統調用。
那么最后的可執行文件到底是什么樣子呢?前面已經說過,這里我們不深入分析ELF文件的格式,只是給出它的一個結構圖和一些簡單的說明,以方便大家理解。
ELF文件格式包括三種主要的類型:可執行文件、可重定向文件、共享庫。
Linux下,我們可以用gcc -c編譯源文件時可將其編譯成*.o格式。
一個ELF文件從連接器(Linker)的角度看,是一些節的集合;從程序加載器(Loader)的角度看,它是一些段(Segments)的集合。ELF格式的程序和共享庫具有相同的結構,只是段的集合和節的集合上有些不同。
那么到底什么是庫呢?
庫從本質上來說是一種可執行代碼的二進制格式,可以被載入內存中執行。庫分靜態庫和動態庫兩種。
靜態庫:這類庫的名字一般是 libxxx.a,xxx為庫的名字。利用靜態函數庫編譯成的文件比較大,因為整個函數庫的所有數據都會被整合進目標代碼中,他的優點就顯而易見了,即編譯后的執行程序不需要外部的函數庫支持,因為所有使用的函數都已經被編譯進去了。當然這也會成為他的缺點,因為如果靜態函數庫改變了,那么你的程序必須重新編譯。
動態庫:這類庫的名字一般是libxxx.M.N.so,同樣的xxx為庫的名字,M是庫的主版本號,N是庫的副版本號。當然也可以不要版本號,但名字必須有。相對于靜態函數庫,動態函數庫在編譯的時候并沒有被編譯進目標代碼中,你的程序執行到相關函數時才調用該函數庫里的相應函數,因此動態函數庫所產生的可執行文件比較小。由于函數庫沒有被整合進你的程序,而是程序運行時動態的申請并調用,所以程序的運行環境中必須提供相應的庫。動態函數庫的改變并不影響你的程序,所以動態函數庫的升級比較方便。linux系統有幾個重要的目錄存放相應的函數庫,如/lib /usr/lib。
庫文件的命名
在 linux 下,庫文件一般放在/usr/lib和/lib下,
靜態庫的名字一般為 libxxxx.a,其中 xxxx 是該lib的名稱;
動態庫的名字一般為 libxxxx.so.major.minor,xxxx 是該lib的名稱,major是主版本號,minor是副版本號
當要使用靜態的程序庫時,連接器會找出程序所需的函數,然后將它們拷貝到執行文件,由于這種拷貝是完整的,所以一旦連接成功,靜態程序庫也就不再需要了。然而,對動態庫而言,就不是這樣。動態庫會在執行程序內留下一個標記指明當程序執行時,首先必須載入這個庫。由于動態庫節省空間,linux下進行連接的缺省操作是首先連接動態庫,也就是說,如果同時存在靜態和動態庫,不特別指定的話,將與動態庫相連接。
Linux 下標準庫鏈接的三種方式(全靜態 , 半靜態 (libgcc,libstdc++), 全動態)
三種標準庫鏈接方式的選項及區別
| -static -pthread -lrt -ldl | 不會發生應用程序在 不同 Linux 版本下的標準庫不兼容問題。 | 生成的文件比較大, 應用程序功能受限(不能調用動態庫等) |
| -pthread -lrt -ldl | 生成文件是三者中最小的 | 比較容易發生應用程序在? 不同 Linux 版本下標準庫依賴不兼容問題。 |
| -static-libgcc -L. -pthread -lrt -ldl | 靈活度大,能夠針對不同的標準庫采取不同的鏈接策略, 從而避免不兼容問題發生。 結合了全靜態與全動態兩種鏈接方式的優點。 | 比較難識別哪些庫容易發生不兼容問題, 目前只有依靠經驗積累。 某些功能會因選擇的標準庫版本而喪失。 |
上述三種標準庫鏈接方式中,比較特殊的是?半靜態鏈接方式。
如何判斷一個程序有沒有鏈接動態庫?
file命令、ldd命令
(1)file命令。file程序是用來判斷文件類型的,啥文件一看都清楚明了。
(2)ldd命令。使用ldd命令來查看程序都依賴哪些動態庫。如果目標程序沒有鏈接動態庫,則打印“not a dynamic executable” (不是動態可執行文件)
obj文件的格式和組成可能是系統差異性的一大體現,比如windows下的PE、linux和一些unix下的elf、macos的mach-o、aix下的xcoff。
查看obj文件的符號表信息,可以通過nm objdump readelf等方法。
Linux下庫文件是如何產生的?
靜態庫
(1)靜態庫。靜態庫的后綴是.a 文件,它的產生分兩步
? ? ? ? Step 1: 由源文件編譯生成一堆.o 文件,每個.o 文件里都包含這個編譯單元的符號表
? ? ? ? Step 2: ar命令 將很多.o轉換成.a,即生成的靜態庫
? ? ? ??
? ? ? ? 靜態庫的制作和使用
動態庫
(2)動態庫。動態庫的后綴是.so,它由gcc加特定參數編譯產生。
? ? ? ??動態庫的后綴為.so,一般存放在/lib, /usr/lib等目錄下,可以使用ldd命令來查看一個可執行程序使用了哪些動態庫。
? ? ? ? 動態庫是由操作系統運行程序時調用的,既然是程序,那按照Linux的風格,肯定有對應的配置文件來設置。動態鏈接庫的配置文件位于/etc/ld.so.conf,具體內容如下:
從上圖可以知道,系統搜索動態鏈接庫的路徑位于特定的目錄。Linux系統為了提高動態鏈接庫的運行性能,把一些常用的動態鏈接庫放在/etc/ld.so.cache這個文件中去。如何更新這個動態鏈接庫的緩存文件呢?Linux提供了ldconfig命令來更新,這個命令主要搜索/lib和/usr/lib以及配置文件ld.so.conf.d/目錄下的可用的動態鏈接庫文件,然后重新創建新的動態鏈接程序/lib/ld-linux.so.2所需的連接和更新動態鏈接庫緩存文件ld.so.cache.
ldconfig –p | head or grep XX : 查看系統中有哪些動態鏈接庫
ldconfig –v 輸出動態鏈接庫掃描目錄并且刷新ld.so.cache緩存
執行ldconfig需要root權限。
LD_LIBRARY_PATH:這個環境變量指示動態連接器可以裝載動態庫的路徑。 當然如果有root權限的話,可以修改/etc/ld.so.conf文件,然后調用 /sbin/ldconfig來達到同樣的目的, 不過如果沒有root權限,那么只能采用修改LD_LIBRARY_PATH環境變量的方法了。
靜態鏈接庫 和 動態連接庫 的環境變量
LIBRARY_PATH環境變量:指定程序靜態鏈接庫文件搜索路徑
LD_LIBRARY_PATH環境變量:指定程序動態鏈接庫文件搜索路徑
首先用gcc編繹該文件,在編繹時可以使用任何合法的編繹參數,例如-g加入調試代碼等:
gcc -c hello.c -o hello.o1、生成靜態庫?生成靜態庫使用ar工具,其實ar是archive的意思
ar cqs libhello.a hello.o2、生成動態庫?用gcc來完成,由于可能存在多個版本,因此通常指定版本號:
gcc -shared -o libhello.so.1.0 hello.o可執行程序在執行的時候如何定位共享庫(動態庫)文件
當系統加載可執行代碼(即庫文件)的時候, 能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑,此時就需要系統動態載入器(dynamic linker/loader)。對于 elf 格式的可執行程序,是由 ld-linux.so* 來完成的,它先后搜索 elf 文件的DT_RPATH 段-->環境變量LD_LIBRARY_PATH—->/etc/ld.so.cache 文件列表--> /lib/,/usr/lib 目錄找到庫文件后將其載入內存 如: export LD_LIBRARY_PATH=`pwd` 將當前文件目錄添加為共享目錄。( 反引號 就是 鍵盤上 esc 按鍵下面的那個符號)export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:`pwd` //最好使用這條命令使用ldd工具,查看可執行程序依賴那些動態庫或著動態庫依賴于那些動態庫: ldd 命令可以查看一個可執行程序依賴的共享庫, 例如 # ldd /bin/lnlibc.so.6 => /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2 => /lib/ld- linux.so.2 (0×40000000) 可以看到 ln 命令依賴于 libc 庫和 ld-linux 庫 使用nm工具,查看靜態庫和動態庫中有那些函數名;
(T類表示函數是當前庫中定義的,U類表示函數是被調用的,在其它庫中定義的,W類是當前庫中定義,被其它庫中的函數覆蓋)。:
有時候可能需要查看一個庫中到底有哪些函數,nm工具可以打印出庫中的涉及到的所有符號,這里的庫既可以是靜態的也可以是動態的。
nm列出的符號有很多, 常見的有三種::
??? T類:是在庫中定義的函數,用T表示,這是最常見的;
??? U類:是 在庫中被調用,但并沒有在庫中定義(表明需要其他庫支持), 用U表示;
??? W類:是所謂的“弱態”符號,它們雖然在庫中被定義,但是可能被其他庫中的同名符號覆蓋,用W表示。
例如,假設開發者希望知道上文提到的hello庫中是否引用了 printf():
??????? nm libhello.so | grep printf
發現printf是U類符號,說明printf被引用,但是并沒有在庫中定義。
由此可以推斷,要正常使用hello庫,必須有其它庫支持,使用ldd工具查看hello依賴于哪些庫:
ldd libhello.so
libc.so.6=>/lib/libc.so.6(0x400la000)
/lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000)
從上面的結果可以繼續查看printf最終在哪里被定義,有興趣可以go on
使用ar工具,可以生成靜態庫,同時可以查看靜態庫中包含那些.o文件,即有那些源文件構成。
可以使用 ar -t libname.a 來查看一個靜態庫由那些.o文件構成。
可以使用 ar q libname.a xxx1.o xxx2.o xxx3.o ... xxxn.o 生成靜態庫
如何查看動態庫和靜態庫是32位,還是64位下的庫:
如果是動態庫,可以使用file *.so;
如果是靜態哭,可以使用objdump -x *.a
使用動態庫和靜態庫
既然是動態鏈接庫,那就是需要開發出來給其他人使用的。按照C語言的風格,h頭文件提供函數庫的接口說明,就像stdio.h頭文件一樣,我們用到的輸入輸出,都必須包含這個頭文件。要使用我們自己的動態鏈接庫,那就要包含動態鏈接庫提供的頭文件。
然后是編譯生成動態鏈接庫
使用動態鏈接庫
??????? OK,有了這些知識,接下來大家就可以弄明白我所做的事情是干什么了。都說例子是最好老師,我們就從例子入手。
靜態鏈接庫
?? 我們先制作自己的靜態鏈接庫,然后再使用它。制作靜態鏈接庫的過程中要用到gcc和ar命令。
? 準備兩個庫的源碼文件st1.c和st2.c,用它們來制作庫libmytest.a,如下:
??? 靜態庫文件libmytest.a已經生成,用file命令查看其屬性,發現它確實是歸檔壓縮文件。用ar -t libmytest.a可以查看一個靜態庫包含了那些obj文件:
??? 接下來我們就寫個測試程序來調用庫libmytest.a中所提供的兩個接口print1()和print2()。
??? 看到沒,靜態庫的編寫和調用就這么簡單,學會了吧。這里gcc的參數-L是告訴編譯器庫文件的路徑是當前目錄,-l是告訴編譯器要使用的庫的名字叫mytest。
動態庫
??? 靜態庫*.a文件的存在主要是為了支持較老的a.out格式的可執行文件而存在的。目前用的最多的要數動態庫了。
動態庫的后綴為*.so。在Linux發行版中大多數的動態庫基本都位于/usr/lib和/lib目錄下。在開發和使用我們自己動態庫之前,請容許我先落里羅嗦的跟大家嘮叨嘮叨Linux下和動態庫相關的事兒吧。
有時候當我們的應用程序無法運行時,它會提示我們說它找不到什么樣的庫,或者哪個庫的版本又不合它胃口了等等之類的話。
??????? 那么應用程序它是怎么知道需要哪些庫的呢?
??????? 可以使用 ldd 命令來查看一個程序文件到底依賴了那些so庫文件。
Linux系統中動態鏈接庫的配置文件一般在/etc/ld.so.conf文件內,它里面存放的內容是可以被Linux共享的動態聯庫所在的目錄的名字。我的系統中,該文件的內容如下:
??? 然后/etc/ld.so.conf.d/目錄下存放了很多*.conf文件,如下:
??? 其中每個conf文件代表了一種應用的庫配置內容,以MySQL為例:
??? 如果您是和我一樣裝的CentOS6.0的系統,那么細心的讀者可能會發現,在/etc目錄下還存在一個名叫ld.so.cache的文件。從名字來看,我們知道它肯定是動態鏈接庫的什么緩存文件。
對,您說的一點沒錯。為了使得動態鏈接庫可以被系統使用,當我們修改了/etc/ld.so.conf或/etc/ld.so.conf.d/目錄下的任何文件,或者往那些目錄下拷貝了新的動態鏈接庫文件時,都需要運行一個很重要的命令:ldconfig,該命令位于/sbin目錄下,主要的用途就是負責搜索/lib和/usr/lib,以及配置文件/etc/ld.so.conf里所列的目錄下搜索可用的動態鏈接庫文件,然后創建出動態加載程序/lib/ld-linux.so.2所需要的連接和(默認)緩存文件/etc/ld.so.cache(此文件里保存著已經排好序的動態鏈接庫名字列表)。
也就是說:當用戶在某個目錄下面創建或拷貝了一個動態鏈接庫,若想使其被系統共享,可以執行一下"ldconfig目錄名"這個命令。此命令的功能在于讓ldconfig將指定目錄下的動態鏈接庫被系統共享起來,即:在緩存文件/etc/ld.so.cache中追加進指定目錄下的共享庫。請注意:如果此目錄不在/lib,/usr/lib及/etc/ld.so.conf文件所列的目錄里面,則再次單獨運行ldconfig時,此目錄下的動態鏈接庫可能不被系統共享了。單獨運行ldconfig時,它只會搜索/lib、/usr/lib以及在/etc/ld.so.conf文件里所列的目錄,用它們來重建/etc/ld.so.cache。
因此,等會兒我們自己開發的共享庫就可以將其拷貝到/lib、/etc/lib目錄里,又或者修改/etc/ld.so.conf文件將我們自己的庫路徑添加到該文件中,再執行ldconfig命令。
非了老半天功夫,終于把基礎打好了,猴急的您早已按耐不住激情的想動手嘗試了吧!哈哈。。。OK,說整咱就開整,接下來我就帶領大家一步一步來開發自己的動態庫,然后教大家怎么去使用它。
我們有一個頭文件my_so_test.h和三個源文件test_a.c、test_b.c和test_c.c,將他們制作成一個名為libtest.so的動態鏈接庫文件:
OK,萬事俱備,只欠東風。如何將這些文件編譯成一個我們所需要的so文件呢?可以分兩步來完成,也可以一步到位:
方法一:
???????? 1、先生成目標.o文件:
?????? 2、再生成so文件:
-shared該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接。相當于一個可執行文件。
-fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯后的代碼是位置相關的,當動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。
方法二:一步到位。
至此,我們制作的動態庫文件libtest.so就算大功告成了。
接下來,就是如何使用這個動態庫了。動態鏈接庫的使用有兩種方法:既可以在運行時對其進行動態鏈接,又可以動態加載在程序中是用它們。接下來,我就這兩種方法分別對其介紹。
==================================================
方法1
? ? 1. 直接使用點c文件生成點so文件gcc -fpic -shared -o xxx.so *.c
? ? 2. 調用gcc -o mainapp main.c ./xxx.so
方法2 生成動態庫(共享庫)
? ? 1. 先生成與位置無關的 .O(小寫o)文件. gcc –fpic -c *.c
? ? ? ?(或者把所有的c文件都寫出來,或者點c文件一個一個生成點o文件)
? ? 2. 再使用點o文件生成動態庫 gcc –shared –o libmylib.so *.o
? ? 3. 使用庫生成可執行文件 gcc main.c –o app –l mylib –L ./ -I ./
===================================================
? ? ? ??+++動態庫的使用+++
????????用法一:動態鏈接。
使用“-ltest”標記來告訴GCC驅動程序在連接階段引用共享函數庫libtest.so。“-L.”標記告訴GCC函數庫可能位于當前目錄。否則GNU連接器會查找標準系統函數目錄。
這里我們注意,ldd的輸出它說我們的libtest.so它沒找到。還記得我在前面動態鏈接庫一節剛開始時的那堆嘮叨么,現在你應該很明白了為什么了吧。因為我們的libtest.so既不在/etc/ld.so.cache里,又不在/lib、/usr/lib或/etc/ld.so.conf所指定的任何一個目錄中。怎么辦?還用我告訴你?管你用啥辦法,反正我用的 ldconfig `pwd`搞定的:
???????執行結果如下:
庫環境變量設置
偶忍不住又要羅嗦一句了,相信俺,我的嘮叨對大家是有好處。我為什么用這種方法呢?因為我是在給大家演示動態庫的用法,完了之后我就把libtest.so給刪了,然后再重構ld.so.cache,對我的系統不會任何影響。倘若我是開發一款軟件,或者給自己的系統DIY一個非常有用的功能模塊,那么我更傾向于將libtest.so拷貝到/lib、/usr/lib目錄下,或者我還有可能在/usr/local/lib/目錄下新建一文件夾xxx,將so庫拷貝到那兒去,并在/etc/ld.so.conf.d/目錄下新建一文件mytest.conf,內容只有一行“/usr/local/lib/xxx/libtest.so”,再執行ldconfig。如果你之前還是不明白怎么解決那個“not found”的問題,那么現在總該明白了吧。
其實原因就是:因為在動態函數庫使用時,會查找/usr/lib、/lib目錄下的動態函數庫,而此時我們生成的庫不在里邊。
這個時候有好幾種方法可以讓他成功運行:
(LD_LIBRARY_PATH 就是 lib_dynamic_library_path 縮寫,只不過都是大寫。)
(1)最直接最簡單的方法就是把so拉到/usr/lib或/lib中去,但這好像有點污染環境吧?
(2)export LD_LIBRARY_PATH=$(pwd) ? ? 或者 上面的?ldconfig `pwd` ??
? ? ? ?或者??export LD_LIBRARY_PATH=/庫的絕對路徑
? ? ??LD_LIBRARY_PATH變量中庫的路徑是以分號分割的。可以 echo $LD_LIBRARY_PATH 查看。
? ? ? ?推薦使用?export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:`pwd` ??? ??
? ? ? ??$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ? ? ? //?LD_LIBRARY_PATH只對當前終端有效,再啟動一個終端無效
(3)可以在/etc/ld.so.conf文件里加入我們生成的庫的目錄,然后/sbin/ldconfig
運行期間查找動態庫
運行期間,系統需要知道到哪里去查找動態庫,這是通過/etc/ld.so.conf配置的。ldconfig用于配置運行時動態庫查找路徑,實際是更新/etc/ld.so.cache。另外一些環境變量也可以影響查找:(Linux/Solaris:?LD_LIBRARY_PATH, SGI:?LD_LIBRARYN32_PATH, AIX:?LIBPATH, Mac OS X:?DYLD_LIBRARY_PATH, HP-UX:?SHLIB_PATH)
靜態庫鏈接時搜索路徑順序:
1. ld會去找GCC命令中的參數-L
2. 再找gcc的環境變量LIBRARY_PATH
3. 再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程序內的
動態鏈接時、執行時搜索路徑順序:
1. 編譯目標代碼時指定的動態庫搜索路徑;
2. 環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑;
3. 配置文件/etc/ld.so.conf中指定的動態庫搜索路徑;
4. 默認的動態庫搜索路徑/lib;
5. 默認的動態庫搜索路徑/usr/lib。
gcc中鏈接 -lxxx庫時,默認會從/lib/和/usr/lib這兩個地方去找鏈接庫,不需要額外設置,若需要加入其他鏈接庫可以通過三種方式:
1.在/etc/目錄下有ld.so.conf、ld.so.cache和ld.so.conf.d/,其中ld.so.conf.d目錄下又有多個*.conf的配置文件。
在ld.so.conf文件中只有一句include /etc/ld.so.conf.d/*.conf,包含ld.so.conf.d目錄下所有的配置文件,需要只需要將自己新增的鏈接庫目錄加入任何一個配置文件,或新建一個自己的配置文件。
只修改配置文件還不行,因為為了提高搜索的效率,所以系統預先對所有配置文件生成了一個二進制的處理文件,也就是ld.so.cache,所以需要運行ldconfig手動更新這個文件。
2.設置LD_LIBRARY_PATH參數,利用export $LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/xxx增加自己的庫路徑
3. 在編譯gcc參數時直接利用-L參數指明鏈接庫路徑
這里插一句,有一次將/lib目錄下的libc.so.6誤刪了,其實這一個軟鏈接,指向的是實際的glibc庫文件,本想將其指向一個版本更高的庫文件,結果刪除這個鏈接后發現所有的命令都不能執行,都依賴這個庫文件,所以使用LD_PRELOAD=/lib/libc-2.6.1.so ln -s libc2.6.1.so libc.so.6.這里LD_PRELOAD就是預加載指定的庫文件。
關于LD_PRELOAD,后面鏈接還有一篇有意思的文章。
示例操作(設置共享庫加載路徑)
打開共享庫路徑配置文件:sudo vi /etc/ld.so.conf
最后一行添加mycal路徑:/home/username/calc
更新共享庫加載路徑:sudo ldconfig -v
此時可以看到自動創建出來了soname libcalc.so.1
手動添加link name:ln -s libcalc.so.1.10 libcalc.so
關于/etc/ld.so.conf
/etc/ld.so.conf里面存放的是鏈接器和加載器搜索共享庫時要檢查的目錄,默認是從/usr/lib /lib中讀取的,所以想要順利運行,我們也可以把我們庫的目錄加入到這個文件中并執行/sbin/ldconfig 。
關于/etc/ld.so.cache
/etc/ld.so.cache里面保存了常用的動態函數庫,且會先把他們加載到內存中,因為內存的訪問速度遠遠大于硬盤的訪問速度,這樣可以提高軟件加載動態函數庫的速度了。
使用了第(2)種方法解決問題
????方法二:動態加載。
動態加載是非常靈活的,它依賴于一套Linux提供的標準API來完成。在源程序里,你可以很自如的運用API來加載、使用、釋放so庫資源。以下函數在代碼中使用需要包含頭文件:dlfcn.h
| 函數原型 | 說明 |
| const char *dlerror(void) | 當動態鏈接庫操作函數執行失敗時,dlerror可以返回出錯信息,返回值為NULL時表示操作函數執行成功。 |
| void *dlopen(const char *filename, int flag) | 用于打開指定名字(filename)的動態鏈接庫,并返回操作句柄。調用失敗時,將返回NULL值,否則返回的是操作句柄。 |
| void *dlsym(void *handle, char *symbol) | 根據動態鏈接庫操作句柄(handle)與符號(symbol),返回符號對應的函數的執行代碼地址。由此地址,可以帶參數執行相應的函數。 |
| int dlclose (void *handle) | 用于關閉指定句柄的動態鏈接庫,只有當此動態鏈接庫的使用計數為0時,才會真正被系統卸載。2.2在程序中使用動態鏈接庫函數。 |
???????dlsym(void *handle, char *symbol)
filename:如果名字不以“/”開頭,則非絕對路徑名,將按下列先后順序查找該文件。
???(1)用戶環境變量中的LD_LIBRARY值;
???(2)動態鏈接緩沖文件/etc/ld.so.cache
???(3)目錄/lib,/usr/lib
??????flag表示在什么時候解決未定義的符號(調用)。取值有兩個:
????? ?1)?RTLD_LAZY :?表明在動態鏈接庫的函數代碼執行時解決。
? ? ? ?2)?RTLD_NOW :表明在dlopen返回前就解決所有未定義的符號,一旦未解決,dlopen將返回錯誤。
????????dlsym(void *handle, char *symbol)
????????dlsym()的用法一般如下:
? ? ? ? void(*add)(int x,int y);?/*說明一下要調用的動態函數add */
? ? ? ? add=dlsym("xxx.so","add");?/*?打開xxx.so共享庫,取add函數地址?*/
? ? ? ? add(89,369);?/*?帶兩個參數89和369調用add函數?*/
??? 看我出招:
????????執行結果:
? ? ? ? ?OK,通過本文的指導、練習相信各位應該對Linux的庫機制有了些許了解,最主要的是會開發使用庫文件了。由于本人知識所限,文中某些觀點如果不到位或理解有誤的地方還請各位個人不吝賜教。
動態加載和卸載的庫
如果需要把應用程序設計成插件化的架構,這就需要可以動態加載和卸載庫的機制。與動態鏈接不同的是,動態加載的意思是,編譯期間可以對動態庫的存在一無所知,而是在運行期間通過用戶程序嘗試加載進來的。通過?dlfcn.h 中的?dlopen?、dlsym?和?dlclose?等函數實現此種功能。另外,使用到dlfcn 機制的可執行文件需要使用 -rdynamic 選項,它將指示連接器把所有符號(而不僅僅只是程序已使用到的外部符號,但不包括靜態符號,比如被static修飾的函數)都添加到動態符號表(即.dynsym表)里。
***********************************************************************************
ldconfig、ldd 命令工具
一、ldconfig
ldconfig是一個動態鏈接庫管理命令。為了讓動態鏈接庫為系統所共享,需運行動態鏈接庫的管理命令--ldconfig。 ldconfig 命令的用途,主要是在默認搜尋目錄(/lib和/usr/lib)以及動態庫配置文件/etc/ld.so.conf內所列的目錄下,搜索出可共享的動態鏈接庫(格式lib*.so*),進而創建出動態裝入程序(ld.so)所需的連接和緩存文件。緩存文件默認為 /etc/ld.so.cache,此文件保存已排好序的動態鏈接庫名字列表,為了讓動態鏈接庫為系統所共享,需運行動態鏈接庫的管理命令ldconfig,此執行程序存放在/sbin目錄下。ldconfig通常在系統啟動時運行,而當用戶安裝了一個新的動態鏈接庫,修改了ld.so.conf時,就需要手工運行這個命令。
linux下的共享庫機制采用了類似于高速緩存的機制,將庫信息保存在/etc/ld.so.cache里邊。程序連接的時候首先從這個文件里邊查找,然后再到ld.so.conf的路徑里邊去詳細找
ldconfig命令行用法如下:
ldconfig [-v|--verbose] [-n] [-N] [-X] [-f CONF] [-C CACHE] [-rROOT] [-l] [-p|--print-cache]
[-c FORMAT] [--format=FORMAT] [-V] [-?|--help|--usage] path...
ldconfig可用的選項說明如下:
(1) -v或--verbose : 用此選項時,ldconfig將顯示正在掃描的目錄及搜索到的動態鏈接庫,還有它所創建的鏈接的名字.
(2) -n : 用此選項時,ldconfig僅掃描命令行指定的目錄,不掃描默認目錄(/lib,/usr/lib),也不掃描配置文件/etc/ld.so.conf所列的目錄.
(3) -N : 此選項指示ldconfig不重建緩存文件(/etc/ld.so.cache).若未用-X選項,ldconfig照常更新文件的連接.
(4) -X : 此選項指示ldconfig不更新文件的連接.若未用-N選項,則緩存文件正常更新.
(5) -f CONF : 此選項指定動態鏈接庫的配置文件為CONF,系統默認為/etc/ld.so.conf.
(6) -C CACHE : 此選項指定生成的緩存文件為CACHE,系統默認的是/etc/ld.so.cache,此文件存放已排好序的可共享的動態鏈接庫的列表.
(7)? -r ROOT : 此選項改變應用程序的根目錄為ROOT(是調用chroot函數實現的).選擇此項時,系統默認的配置文件 /etc/ld.so.conf,實際對應的為 ROOT/etc/ld.so.conf.如用-r /usr/zzz時,打開配置文件 /etc/ld.so.conf時,實際打開的是/usr/zzz/etc/ld.so.conf文件.用此選項,可以大大增加動態鏈接庫管理的靈活性.
(8) -l : 通常情況下,ldconfig搜索動態鏈接庫時將自動建立動態鏈接庫的連接.選擇此項時,將進入專家模式,需要手工設置連接.一般用戶不用此項.
(9) -p或--print-cache : 此選項指示ldconfig打印出當前緩存文件所保存的所有共享庫的名字.
(10) -c FORMAT 或 --format=FORMAT : 此選項用于指定緩存文件所使用的格式,共有三種: ld(老格式),new(新格式)和compat(兼容格式,此為默認格式).
(11) -V : 此選項打印出ldconfig的版本信息,而后退出.
(12) -? 或 --help 或--usage : 這三個選項作用相同,都是讓ldconfig打印出其幫助信息,而后退出.
ldconfig幾個需要注意的地方?
1. 往/lib和/usr/lib里面加東西,是不用修改/etc/ld.so.conf的,但是完了之后要調一下ldconfig,不然這個library會找不到?
2. 想往上面兩個目錄以外加東西的時候,一定要修改/etc/ld.so.conf,然后再調用ldconfig,不然也會找不到?
比如安裝了一個MySQL到/usr/local/MySQL,mysql有一大堆library在/usr/local/mysql/lib下面,這時 就需要在/etc/ld.so.conf下面加一行/usr/local/mysql/lib,保存過后ldconfig一下,新的library才能在 程序運行時被找到。?
3. 如果想在這兩個目錄以外放lib,但是又不想在/etc/ld.so.conf中加東西(或者是沒有權限加東西)。那也可以,就是export一個全局變 量LD_LIBRARY_PATH,然后運行程序的時候就會去這個目錄中找library。一般來講這只是一種臨時的解決方案,在沒有權限或臨時需要的時 候使用。?
4. ldconfig做的這些東西都與運行程序時有關,跟編譯時一點關系都沒有。編譯的時候還是該加-L就得加,不要混淆了。?
5. 總之,就是不管做了什么關于library的變動后,最好都ldconfig一下,不然會出現一些意想不到的結果。不會花太多的時間,但是會省很多的事。
ldconfig提示“is not asymbolic link”的解決方法
在編譯的時候會出現以下錯誤:
ldconfig?
ldconfig: /lib/libdb-4.7.so is not a symbolic link
這是因為正常情況下libdb-4.7.so是一個符號連接,而不是一個實體文件,因此只需要把它改成符號連接即可
mv libdb-4.7.so libdb-4.so.7
ln -s libdb-4.so.7 libdb-4.7.so
二、ldd
作用:用來查看程序運行所需的共享庫,常用來解決程序因缺少某個庫文件而不能運行的一些問題。
語法:ldd(選項)(參數)
選項:
--version:打印指令版本號;
-v:詳細信息模式,打印所有相關信息;?
-u:打印未使用的直接依賴;
-d:執行重定位和報告任何丟失的對象;?
-r:執行數據對象和函數的重定位,并且報告任何丟失的對象和函數;?
--help:顯示幫助信息。
參數:文件:指定可執行程序或者文庫。
ldd命令原理介紹:
1、首先ldd不是一個可執行程序,而只是一個shell腳本
2、ldd能夠顯示可執行模塊的dependency,其原理是通過設置一系列的環境變量,如下:LD_TRACE_LOADED_OBJECTS、LD_WARN、LD_BIND_NOW、LD_LIBRARY_VERSION、LD_VERBOSE等。當
LD_TRACE_LOADED_OBJECTS環境變量不為空時,任何可執行程序在運行時,它都會只顯示模塊的dependency,而程序并不真正執行。要不你可以在shell終端測試一下,如下:
(1) export LD_TRACE_LOADED_OBJECTS=1
(2) 再執行任何的程序,如ls等,看看程序的運行結果
3、ldd顯示可執行模塊的dependency的工作原理,其實質是通過ld-linux.so(elf動態庫的裝載器)來實現的。我們知道,ld-linux.so模塊會先于executable模塊程序工作,并獲得控制權,因此當上述的那些環境變量被設置時,ld-linux.so選擇了顯示可執行模塊的dependency。
4、實際上可以直接執行ld-linux.so模塊,如:/lib/ld-linux.so.2 --list program(這相當于ldd program)
向大家推薦一個linux下系統命令幫助查閱的網站,網站是中文的,拯救了一大波英文不好的,而且介紹很齊全,排版很簡潔:
http://man.linuxde.net/
Linux下進行程序設計時,關于庫的使用
一、gcc/g++命令中關于庫的參數:
-shared: 該選項指定生成動態連接庫; -fPIC:表示編譯為位置獨立(地址無關)的代碼,不用此選項的話,編譯后的代碼是位置相關的,所以動態載入時,是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。 -L:指定鏈接庫的路徑, -L. 表示要連接的庫在當前目錄中 -ltest:指定鏈接庫的名稱為test,編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,后面加上.so來確定庫的名稱 -Wl,-rpath: 記錄以來so文件的路徑信息。 LD_LIBRARY_PATH:這個環境變量指示動態連接器可以裝載動態庫的路徑。 當然如果有root權限的話,可以修改/etc/ld.so.conf文件,然后調用 /sbin/ldconfig來達到同樣的目的,不過如果沒有root權限,那么只能采用修改LD_LIBRARY_PATH環境變量的方法了。
調用動態庫的時候,有幾個問題會經常碰到:
? ? ? ? 有時,明明已經將庫的頭文件所在目錄 通過 “-I” include進來了,庫所在文件通過 “-L”參數引導,并指定了“-l”的庫名,但通過ldd命令察看時,就是死活找不到你指定鏈接的so文件,這時你要作的就是通過修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件來指定動態庫的目錄。通常這樣做就可以解決庫無法鏈接的問題了。
二、靜態庫鏈接時搜索路徑的順序
1. ld會去找gcc/g++命令中的參數-L;
2. 再找gcc的環境變量LIBRARY_PATH,它指定程序靜態鏈接庫文件搜索路徑;
? ? ? ? export LIBRARY_PATH=$LIBRARY_PATH:data/home/billchen/lib
3. 再找默認庫目錄 /lib /usr/lib /usr/local/lib,這是當初compile gcc時寫在程序內的。
三、動態鏈接時、執行時搜索路徑順序
1. 編譯目標代碼時指定的動態庫搜索路徑;
2. 環境變量LD_LIBRARY_PATH指定動態庫搜索路徑,它指定程序動態鏈接庫文件搜索路徑;
? ? ? ? export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:data/home/billchen/lib
3. 配置文件/etc/ld.so.conf中指定的動態庫搜索路徑;
4. 默認的動態庫搜索路徑/lib;
5. 默認的動態庫搜索路徑/usr/lib。
四、靜態庫和動態鏈接庫同時存在時,gcc/g++默認鏈接的是動態庫
當一個庫同時存在靜態庫和動態庫時,比如 libmysqlclient.a 和 libmysqlclient.so 同時存在時:
在Linux下,動態庫和靜態庫同時存在時,gcc/g++的鏈接程序,默認鏈接的動態庫。
可以使用下面的方法,給連接器ld傳遞參數,看是否鏈接動態庫還是靜態庫。
? ??? ? -Wl,-Bstatic -llibname
? ??? ??//指定讓gcc/g++鏈接靜態庫
? ??? ??使用: gcc/g++ test.c -o test -Wl,-Bstatic -llibname -Wl,-Bdynamic -lm -lc
? ??? ??-Wl,-Bdynamic -llibname
? ??? ??//指定讓gcc/g++鏈接動態庫
? ??? ??使用: gcc/g++ test.c -o test -Wl,-Bdynamic -llibname
如果要完全靜態加載,使用-static參數,即將所有的庫以靜態的方式鏈入可執行程序,這樣生成的可執行程序,不再依賴任何庫,同時出現的問題是,這樣編譯出來的程序非常大,占用空間。
如果不適用-Wl,-Bdynamic -lm -c會有如下錯誤:
[chenbaihu@build17 lib]$ ls libtest.a libtest.so t t.cc test.cc test.h test.o [chenbaihu@build17 lib]$ g++ -Wall -g t.cc -o t -L./ -Wl,-Bstatic -ltest -Wl,-Bdynamic -lm -lc [chenbaihu@build17 lib]$ g++ -Wall -g t.cc -o t -L./ -Wl,-Bstatic -ltest /usr/bin/ld: cannot find -lm collect2: ld 返回 1
參考:
http://lists.gnu.org/archive/html/help-gnu-utils/2004-03/msg00009.html
五、有關環境變量
? ? ? ? LIBRARY_PATH環境變量:指定程序靜態鏈接庫文件搜索路徑
? ? ? ??LD_LIBRARY_PATH環境變量:指定程序動態鏈接庫文件搜索路徑
六、庫的依賴問題
比如我們有一個基礎庫libbase.a,還有一個依賴libbase.a編譯的庫叫做libchild.a。在我們編譯程序時,一定要先-lchild再-lbase。 如果使用 -lbase -lchild,在編譯時將出現一些函數undefined,而這些函數實際上已經在base中已經定義;
為什么會有庫的依賴問題?
一、靜態庫解析符號引用: 鏈接器ld是如何使用靜態庫來解析引用的。在符號解析階段,鏈接器從左至右,依次掃描可重定位目標文件(*.o)和靜態庫(*.a)。 在這個過程中,鏈接器將維持三個集合: 集合E:可重定位目標文件(*.o文件)的集合。 集合U:未解析(未定義)的符號集,即符號表中UNDEF的符號。 集合D: 已定義的符號集。 初始情況下,E、U、D均為空。
1、對于每個輸入文件f,如果是目標文件(.o),則將f加入E,并用f中的符號表修改U、D(在文件f中定義實現的符號是D,在f中引用的符號是U),然后繼續下個文件。
2、如果f是一個靜態庫(.a),那么鏈接器將嘗試匹配U中未解析符號與靜態庫成員(靜態庫的成員就是.o文件)定義的符號。如果靜態庫中某個成員m(某個.o文件)定義了一個符號來解析U中引用,那么將m加入E中, 同時使用m的符號表,來更新U、D。對靜態庫中所有成員目標文件反復進行該過程,直至U和D不再發生變化。此時,靜態庫f中任何不包含在E中的成員目標文件都將丟棄,鏈接器將繼續下一個文件。
3、當所有輸入文件完成后,如果U非空,鏈接器則會報錯,否則合并和重定位E中目標文件,構建出可執行文件。 到這里,為什么會有庫的依賴問題已經得到解答: 因為libchild.a依賴于libbase.a,但是libbase.a在libchild.a的左邊,導致libbase.a中的目標文件(*.o)根本就沒有被加載到E中,所以解決方法就是交換兩者的順序。當然也可以使用-lbase -lchild -lbase的方法。
參考文章:http://pananq.com/index.php/page/3/
七、動態庫升級問題
在動態鏈接庫升級時,不能使用cp newlib.so oldlib.so,這樣有可能會使程序core掉;
而應該使用: rm oldlib.so 然后 cp newlib.so oldlib.so
或者 mv oldlib.so oldlib.so_bak 然后 cp newlib.so oldlib.so
為什么不能用cp newlib.so oldlib.so ?
在替換so文件時,如果在不停程序的情況下,直接用 cp new.so old.so 的方式替換程序使用的動態庫文件會導致正在運行中的程序崩潰。
解決方法:
解決的辦法是采用“rm+cp” 或“mv+cp” 來替代直接“cp” 的操作方法。
linux系統的動態庫有兩種使用方法:運行時動態鏈接庫,動態加載庫并在程序控制之下使用。
1、為什么在不停程序的情況下,直接用 cp 命令替換程序使用的 so 文件,會使程序崩潰? 很多同學在工作中遇到過這樣一個問題,在替換 so 文件時,如果在不停程序的情況下,直接用cp new.so old.so的方式替換程序使用的動態庫文件會導致正在運行中的程序崩潰,退出。這與 cp 命令的實現有關,cp 并不改變目標文件的 inode,cp 的目標文件會繼承被覆蓋文件的屬性而非源文件。實際上它是這樣實現的: strace cp libnew.so libold.so 2>&1 |grep open.*lib.*.so open("libnew.so", O_RDONLY|O_LARGEFILE) = 3 open("libold.so", O_WRONLY|O_TRUNC|O_LARGEFILE) = 4 在 cp 使用“O_WRONLY|O_TRUNC” 打開目標文件時,原 so 文件的鏡像被意外的破壞了。這樣動態鏈接器 ld.so 不能訪問到 so 文件中的函數入口。從而導致 Segmentation fault,程序崩潰。ld.so 加載 so 文件及“再定位”的機制比較復雜。
2、怎樣在不停止程序的情況下替換so文件,并且保證程序不會崩潰? 答案是采用“rm+cp” 或“mv+cp” 來替代直接“cp” 的操作方法。在用新的so文件 libnew.so 替換舊的so文件 libold.so 時,如果采用如下方法: rm libold.so //如果內核正在使用libold.so,那么inode節點不會立刻別刪除掉。 cp libnew.so libold.so 采用這種方法,目標文件 libold.so 的 inode 其實已經改變了,原來的 libold.so 文件雖然不能用"ls"查看到,但其inode并沒有被真正刪除,直到內核釋放對它的引用。(即: rm libold.so,此時,如果ld.so正在加在libold.so,內核就在引用libold.so的inode節點,rm libold.so的inode并沒有被真正刪除,當ld.so對libold.so的引用結束,inode才會真正刪除。這樣程序就不會崩潰,因為它還在使用舊的libold.so,當下次再使用libold.so時,已經被替換,就會使用新的libold.so)
? ? ? ? 同理,mv只是改變了文件名,其 inode 不變,新文件使用了新的 inode。這樣動態鏈接器 ld.so 仍然使用原來文件的 inode 訪問舊的 so 文件。因而程序依然能正常運行。(即: mv libold.so ***后,如果程序使用動態庫,還是使用舊的inode節點,當下次再使用libold.so時,就會使用新的libold.so)
? ? ? ? 到這里,為什么直接使用“cp new_exec_file old_exec_file”這樣的命令時,系統會禁止這樣的操作,并且給出這樣的提示“cp: cannot create regular file `old': Text file busy”。
? ? ? ? 這時,我們采用的辦法仍然是用“rm+cp”或者“mv+cp”來替代直接“cp”,這跟以上提到的so文件的替換有同樣的道理。
? ? ? ? 但是,為什么系統會阻止cp覆蓋可執行程序,而不阻止覆蓋so文件呢?
? ? ? ? 這是因為 Linux 有個 Demand Paging 機制,所謂“Demand Paging”,簡單的說,就是系統為了節約物理內存開銷,并不會程序運行時就將所有頁(page)都加載到內存中,而只有在系統有訪問需求時才將其加載。“Demand Paging”要求正在運行中的程序鏡像(注意,并非文件本身)不被意外修改,因此內核在啟動程序后會鎖定這個程序鏡像的 inode。
? ? ? ? 對于 so 文件,它是靠 ld.so 加載的,而ld.so畢竟也是用戶態程序,沒有權利去鎖定inode,也不應與內核的文件系統底層實現耦合。
========================
GNU Binutils:http://www.gnu.org/software/binutils/
GNU Binutils詳解:http://www.crifan.com/files/doc/docbook/binutils_intro/release/html/binutils_intro.html
交叉編譯詳解:http://www.crifan.com/files/doc/docbook/cross_compile/release/html/cross_compile.html
Binutils工具集 解析:http://blog.csdn.net/zqixiao_09/article/details/50783007
gcc?
gcc - GNU project C and C++ compiler
Linux 下 程序從源文件到編譯成可執行文件流程
gcc部分選項
-E? ? 預處理.預處理之后的代碼將送往標準輸出
-S? ? 編譯為匯編代碼
-c? ? 編譯為目標文件,不連接庫
上面三個選項可以記憶為:ESc ,就是鍵盤上的取消按鍵
-Wwarn... 設置警告,可以設置的警告開關很多,通常用 -Wall 開啟所有的警告 -Olevel設置優化級別,level可以是0,1,2,3或者s,默認-O0,即不進行優化。. -Dname=definition...在命令行上定義宏,有兩種方式,-Dname或者-Dname=definition.在命令行上設置宏定義的目的主要是為了在調試的時候設定一些開關, 而在發布的時候再關閉或者打開這些開關即可,當然宏定義也用來對代碼進行有選擇地編譯.另外也還有其他的一些作用. -Uname 取消宏定義name,作用和上面的正好相反. -Idir...把dir加到頭文件的搜索路徑中,而且gcc會在搜索標準頭文件之前先搜索dir. -llibrary 在連接的時候搜索library庫,庫是一些archieve文件--其成員是目標文件.如果有文件引用library,library在命令行的位置應該在那個文件之后,因此,越底層的庫越要放在后面.比如如果你要連接pcap庫,那么你就需要使用-lpcap對源文件進行編譯. -Ldir...把dir加到庫文件的搜索路徑中,而且gcc會在搜索標準庫文件之前先搜索dir. -pthread通過pthreads庫加入對多線程的支持,這為預處理和連接設置了標志.pthread是POSIX指定的標準線程庫. -std=standard設置采用的標準,該選項是針對C語言的,比如-std=c99表示編譯器遵循C99標準.該選項較少使用.而且有時反而會把你搞糊涂. -o outfile指定輸出文件的文件名,默認為a.out -mmachine-option...指定所用的平臺.
gcc常用選項總結
常規選項
? ? 1、沒有任何選項:gcc helloworld.c
? ? ? ? 結果會在與helloworld.c相同的目錄下產生一個a.out的可執行文件。
? ? 2、-o選項,指定輸出文件名:gcc -o helloworld helloworld.c
? ? ? ? -o 意思是Output即需要指定輸出的可執行文件的名稱。這里的名稱為helloworld。
? ? 3、-c選項,只編譯,不匯編連接:gcc -c helloworld.c
? ? ? ? -c 意思就是Compile,產生一個叫helloworld.o的目標文件
? ? 4、-S選項,產生匯編源文件:gcc -S helloworld.c
? ? ? ? -S 意思就是aSsemble,產生一個叫helloworld.s的匯編源文件
? ? 5、-E選項,預處理C源文件:gcc -E helloworld.c
? ? ? ? -E意思就是prEprocess。輸出不是送到一個文件而是標準輸出。當然可以對它進行重定向:
? ? ? ? gcc -E helloworld.c > helloworld.txt
? ? -llibrary? ?指定所需要的額外庫
? ? -Ldir:? ? ?指定庫搜索路徑?
? ? -static:? ?靜態鏈接所有庫?
? ? -static-libgcc:? ? ? ? ?靜態鏈接 gcc 庫?
? ? -static-libstdc++:? ?靜態鏈接 c++ 庫?
? ? 關于上述命令的詳細說明,請參閱 GCC 技術手冊
-std=c++11?指定使用C++11標準進行編譯。因為上一個代碼中使用了C++11中的std::array?等特性
-I[Dir]?(大寫的字母 i ) 指定頭文件目錄的搜索目錄?
-L[Dir]?(大寫的字母 L) 指定動態鏈接庫的搜索目錄?
-l[lib]?(小寫的字母 L) 指定具體的靜態庫、動態庫是哪個
如果頭文件和源文件在同一目錄下,編譯時 -I(注意是大寫的i)可省略 。gcc 頭文件尋找次序:gcc會在程序當前目錄、/usr/include和/usr/local/include目錄下查找add.h文件。-I是用來告訴gcc去哪里找頭文件的。-L實際上也很類似, 它是用來告訴gcc去哪里找庫文件。 通常來講, gcc默認會在程序當前目錄、/lib、/usr/lib和/usr/local/lib下找對應的庫。-l(注意是小寫的L)的作用就是用來指定具體的靜態庫、動態庫是哪個。
使用示例:
? ? ? ? 假設在 /tmp/project/ 目錄下有三個文件:test.c 、log.c、log.h?
? ? ? ? 現在進入project 目錄下編譯:gcc -o main test.c log.c -I./
? ? 或者
? ? ? ? 先把log.c 和 log.h 編譯成 目標文件:gcc -o log.o log.c?
? ? ? ??再把 log.o 和 test.c 編譯成可執行文件:gcc -o main log.o test.c
優化選項
1) -O選項,基本優化:gcc -O helloworld.c
? ? ? ? -O意思就是Optimize,產生一個經過優化的叫作a.out的可執行文件。也可以同時使用-o選項,以指定輸出文件名。
? ? ? ? 如:gcc -O -o test helloworld.c
? ? ? ? 即會產生一個叫test的經過優化的可執行文件。
2) -O2選項,最大優化:gcc -O2 helloworld.c
? ? ? ? 產生一個經過最大優化的叫作a.out的可執行文件。
調試選項
1) -g選項,產生供gdb調試用的可執行文件:gcc -g helloworld.c
? ? ? ? 產生一個叫作a.out的可執行文件,大小明顯比只用-o選項編譯匯編連接后的文件大。
2) -pg選項,產生供gprof剖析用的可執行文件:gcc -pg helloworld.c
? ? ? ? 產生一個叫作a.out的執行文件,大小明顯比用-g選項后產生的文件還大。
Gcc的錯誤類型及對策
Gcc編譯器如果發現源程序中有錯誤,就無法繼續進行,也無法生成最終的可執行文件。為了便于修改,gcc給出錯誤資訊,我們必須對這些錯誤資訊逐個進行分析、處理,并修改相應的語言,才能保證源代碼的正確編譯連接。gcc給出的錯誤資訊一般可以分為四大類,下面我們分別討論其產生的原因和對策。
第一類∶C語法錯誤
錯誤資訊∶文件source.c中第n行有語法錯誤(syntex errror)。這種類型的錯誤,一般都是C語言的語法錯誤,應該仔細檢查源代碼文件中第n行及該行之前的程序,有時也需要對該文件所包含的頭文件進行檢查。有些情況下,一個很簡單的語法錯誤,gcc會給出一大堆錯誤,我們最主要的是要保持清醒的頭腦,不要被其嚇倒,必要的時候再參考一下C語言的基本教材。
第二類∶頭文件錯誤
錯誤資訊∶找不到頭文件head.h(Can not find include file head.h)。這類錯誤是源代碼文件中的包含頭文件有問題,可能的原因有頭文件名錯誤、指定的頭文件所在目錄名錯誤等,也可能是錯誤地使用了雙引號和尖括號。
第三類∶檔案庫錯誤
錯誤資訊∶連接程序找不到所需的函數庫,例如∶
ld: -lm: No such file or directory
這類錯誤是與目標文件相連接的函數庫有錯誤,可能的原因是函數庫名錯誤、指定的函數庫所在目錄名稱錯誤等,檢查的方法是使用find命令在可能的目錄中尋找相應的函數庫名,確定檔案庫及目錄的名稱并修改程序中及編譯選項中的名稱。
第四類∶未定義符號
錯誤資訊∶有未定義的符號(Undefined symbol)。這類錯誤是在連接過程中出現的,可能有兩種原因∶一是使用者自己定義的函數或者全局變量所在源代碼文件,沒有被編譯、連接,或者干脆還沒有定義,這需要使用者根據實際情況修改源程序,給出全局變量或者函數的定義體;二是未定義的符號是一個標準的庫函數,在源程序中使用了該庫函數,而連接過程中還沒有給定相應的函數庫的名稱,或者是該檔案庫的目錄名稱有問題,這時需要使用檔案庫維護命令ar檢查我們需要的庫函數到底位于哪一個函數庫中,確定之后,修改gcc連接選項中的-l和-L項。
排除編譯、連接過程中的錯誤,應該說這只是程序設計中最簡單、最基本的一個步驟,可以說只是開了個頭。這個過程中的錯誤,只是我們在使用C語言描述一個算法中所產生的錯誤,是比較容易排除的。我們寫一個程序,到編譯、連接通過為止,應該說剛剛開始,程序在運行過程中所出現的問題,是算法設計有問題,說得更玄點是對問題的認識和理解不夠,還需要更加深入地測試、調試和修改。一個程序,稍為復雜的程序,往往要經過多次的編譯、連接和測試、修改。
總結
以上是生活随笔為你收集整理的Linux 动态库和静态库的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python3 爬虫实战:为爬虫添加 G
- 下一篇: linux命令终极系列awk