1. 程式人生 > >《6.C語言巨集定義與預處理、函式和函式庫》

《6.C語言巨集定義與預處理、函式和函式庫》

《6.C語言巨集定義與預處理、函式和函式庫》

第一部分、章節目錄
4.6.1.C語言預處理理論
4.6.2.C語言預處理程式碼實戰
4.6.3.巨集定義1
4.6.4.巨集定義2
4.6.5.函式的本質
4.6.6.函式的基本使用
4.6.7.遞迴函式
4.6.8.函式庫
4.6.9.字串函式
4.6.10.數學庫函式
4.6.11.自己製作靜態連結庫並使用
4.6.12.自己製作動態連結庫並使用

第二部分、章節介紹
4.6.1.C語言預處理理論
本節向大家引入C語言預處理,講解預處理進行的時間段和意義,引入常見的幾種預處理技術及其相關的關鍵字,最後介紹gcc中用於預處理的編譯選項。
4.6.2.C語言預處理程式碼實戰
本節向大家演示幾種常見的預處理,通過程式碼例項讓大家明白這幾種預處理技巧的使用方法和使用環境,目的是使大家真正學會使用這些預處理技巧。
4.6.3.巨集定義1
本節首先講解巨集定義的一般規則和使用方法,然後用兩個巨集定義向大家演示巨集定義中的關鍵點和易錯點,這兩個巨集定義都是面試題中非常常見的典型題目。
4.6.4.巨集定義2
本節深入對比帶參巨集、帶參函式的相同相異點,並且藉此引入行內函數,讓大家明白這三者之間的聯絡,知道在什麼情況下程式設計應該選擇哪種技術來達到最好的效果。
4.6.5.函式的本質
本節首先引入函式,講解函數出現的原因,函式書寫的一般規則,同時擴充套件了函式在面向物件領域出現的形式(方法),最後向大家揭示了函式的本質:資料處理器。
4.6.6.函式的基本使用
本節主講函式三要素:函式定義、函式宣告和函式呼叫。通過三要素讓大家進一步掌握函式的基本功能原則和方法。
4.6.7.遞迴函式
本節講述函式中的一類特殊分支:遞迴函式。通過求階乘的示例讓大家理解遞迴函式的“遞迴”二字的真實含義,並且總結了遞迴函式的要點。
4.6.8.函式庫
本節引入函式庫,告訴大家函式庫的來源和意義,以及常規的使用方法。
4.6.9.字串函式
本節講述函式庫中一類特殊函式:字串函式,這個是程式設計中經常需要用到的一類函式。
4.6.10.數學庫函式
本節講述函式庫中另一個分支:數學庫函式。通過數學庫函式的使用示例引入了-lm這種連結引數,為後面講述動態和靜態連結庫打下了基礎。
4.6.11.自己製作靜態連結庫並使用
本節演示自己製作靜態連結庫,並且使用自己寫的靜態連結庫。
4.6.12.自己製作動態連結庫並使用
本節演示自己編寫編譯動態連結庫,並且使用自己編寫的動態連結庫,通過本節希望大家能夠掌握庫的使用(編譯時、連線時和執行時下的不同要求)。

第三部分、隨堂記錄
4.6.1.C語言預處理理論
4.6.1.1、由原始碼到可執行程式的過程
(1)原始碼.c->(編譯)->elf可執行程式
(2)原始碼.c->(編譯)->目標檔案.o->(連結)->elf可執行程式
(3)原始碼.c->(編譯)->彙編檔案.S->(彙編)->目標檔案.o->(連結)->elf可執行程式
(4)原始碼.c->(預處理)->預處理過的.i原始檔->(編譯)->彙編檔案.S->(彙編)->目標檔案.o->(連結)->elf可執行程式

預處理用前處理器,編譯用編譯器,彙編用匯編器,連結用連結器,這幾個工具再加上其他一些額外的會用到的可用工具,合起來叫編譯工具鏈。gcc就是一個編譯工具鏈。

4.6.1.2、預處理的意義
(1)編譯器本身的主要目的是編譯原始碼,將C的原始碼轉化成.S的彙編程式碼。編譯器聚焦核心功能後,就剝離出了一些非核心的功能到前處理器去了。
(2)前處理器幫編譯器做一些編譯前的雜事。

4.6.1.3、程式設計中常見的預處理
(1)#include(#include <>和#include ""的區別)
(2)註釋
(3)#if #elif #endif #ifdef
(4)巨集定義

4.6.1.4、gcc中只預處理不編譯的方法
(1)gcc編譯時可以給一些引數來做一些設定,譬如gcc xx.c -o xx可以指定可執行程式的名稱;譬如gcc xx.c -c -o xx.o可以指定只編譯不連線,也可以生成.o的目標檔案。
(2)gcc -E xx.c -o xx.i可以實現只預處理不編譯。一般情況下沒必要只預處理不編譯,但有時候這種技巧可以用來幫助我們研究預處理過程,幫助debug程式。

總結:巨集定義被預處理時的現象有:第一,巨集定義語句本身不見了(可見編譯器根本就不認識#define,編譯器根本不知道還有個巨集定義);第二,typedef重新命名語言還在,說明它和巨集定義是有本質區別的(說明typedef是由編譯器來處理而不是前處理器處理的);

4.6.2.C語言預處理程式碼實戰
4.6.2.1、標頭檔案包含
(1)#include <> 和 #include"“的區別:<>專門用來包含系統提供的標頭檔案(就是系統自帶的,不是程式設計師自己寫的),”“用來包含自己寫的標頭檔案;更深層次來說:<>的話C語言編譯器只會到系統指定目錄(編譯器中配置的或者作業系統配置的尋找目錄,譬如在ubuntu中是/usr/include目錄,編譯器還允許用-I來附加指定其他的包含路徑)去尋找這個標頭檔案(隱含意思就是不會找當前目錄下),如果找不到就會提示這個標頭檔案不存在。
(2)”“包含的標頭檔案,編譯器預設會先在當前目錄下尋找相應的標頭檔案,如果沒找到然後再到系統指定目錄去尋找,如果還沒找到則提示檔案不存在。
總結+注意:規則雖然允許用雙引號來包含系統指定目錄,但是一般的使用原則是:如果是系統指定的自帶的用<>,如果是自己寫的在當前目錄下放著用”",如果是自己寫的但是集中放在了一起專門存放標頭檔案的目錄下將來在編譯器中用-I引數來尋找,這種情況下用<>。
(3)標頭檔案包含的真實含義就是:在#include<xx.h>的那一行,將xx.h這個標頭檔案的內容原地展開替換這一行#include語句。過程在預處理中進行。

4.6.2.2、註釋
(1)註釋是給人看的,不是給編譯器看的。
(2)編譯器既然不看註釋,那麼編譯時最好沒有註釋的。實際上在預處理階段,前處理器會拿掉程式中所有的註釋語句,到了編譯器編譯階段程式中其實已經沒有註釋了。

4.6.2.3、條件編譯
(1)有時候我們希望程式有多種配置,我們在原始碼編寫時寫好了各種配置的程式碼,然後給個配置開關,在原始碼級別去修改配置開關來讓程式編譯出不同的效果。
(2)條件編譯中用的兩種條件判定方法分別是#ifdef 和 #if
區別:#ifdef XXX判定條件成立與否時主要是看XXX這個符號在本語句之前有沒有被定義,只要定義了(我們可以直接#define XXX或者#define XXX 12或者#define XXX YYY)這個符號就是成立的。
的格式是:#if (條件表示式),它的判定標準是()中的表示式是否為true還是flase,跟C中的if語句有點像。

4.6.3.巨集定義1
4.6.3.1、巨集定義的規則和使用解析
(1)巨集定義的解析規則就是:在預處理階段由前處理器進行替換,這個替換是原封不動的替換。
(2)巨集定義替換會遞迴進行,直到替換出來的值本身不再是一個巨集為止。
(3)一個正確的巨集定義式子本身分為3部分:第一部分是#dedine ,第二部分是巨集名 ,剩下的所有為第三部分。
(4)巨集可以帶引數,稱為帶參巨集。帶參巨集的使用和帶參函式非常像,但是使用上有一些差異。在定義帶參巨集時,每一個引數在巨集體中引用時都必須加括號,最後整體再加括號,括號缺一不可。

4.6.3.2、巨集定義示例1:MAX巨集,求2個數中較大的一個
#define MAX(a, b) (((a)>(b)) ? (a) : (b))
關鍵:
第一點:要想到使用三目運算子來完成。
第二點:注意括號的使用

4.6.3.3、巨集定義示例2:SEC_PER_YEAR,用巨集定義表示一年中有多少秒
#define SEC_PER_YEAR (3652460*60UL)
關鍵:
第一點:當一個數字直接出現在程式中時,它的是型別預設是int
第二點:一年有多少秒,這個數字剛好超過了int型別儲存的範圍

4.6.4.巨集定義2
4.6.4.1、帶參巨集和帶參函式的區別(巨集定義的缺陷)
(1)巨集定義是在預處理期間處理的,而函式是在編譯期間處理的。這個區別帶來的實質差異是:巨集定義最終是在呼叫巨集的地方把巨集體原地展開,而函式是在呼叫函式處跳轉到函式中去執行,執行完後再跳轉回來。
注:巨集定義和函式的最大差別就是:巨集定義是原地展開,因此沒有呼叫開銷;而函式是跳轉執行再返回,因此函式有比較大的呼叫開銷。所以巨集定義和函式相比,優勢就是沒有呼叫開銷,沒有傳參開銷,所以當函式體很短(尤其是隻有一句話時)可以用巨集定義來替代,這樣效率高。
(2)帶參巨集和帶參函式的一個重要差別就是:巨集定義不會檢查引數的型別,返回值也不會附帶型別;而函式有明確的引數型別和返回值型別。當我們呼叫函式時編譯器會幫我們做引數的靜態型別檢查,如果編譯器發現我們實際傳參和引數宣告不同時會報警告或錯誤。
注:用函式的時候程式設計師不太用操心型別不匹配因為編譯器會檢查,如果不匹配編譯器會叫;用巨集的時候程式設計師必須很注意實際傳參和巨集所希望的引數型別一致,否則可能編譯不報錯但是執行有誤。
總結:巨集和函式各有千秋,各有優劣。總的來說,如果程式碼比較多用函式適合而且不影響效率;但是對於那些只有一兩句話的函式開銷就太大了,適合用帶參巨集。但是用帶參巨集又有缺點:不檢查引數型別。

4.6.4.2、行內函數和inline關鍵字
(1)行內函數通過在函式定義前加inline關鍵字實現。
(2)行內函數本質上是函式,所以有函式的優點(行內函數是編譯器負責處理的,編譯器可以幫我們做引數的靜態型別檢查);但是他同時也有帶參巨集的優點(不用呼叫開銷,而是原地展開)。所以幾乎可以這樣認為:行內函數就是帶了引數靜態型別檢查的巨集。
(3)當我們的函式內函式體很短(譬如只有一兩句話)的時候,我們又希望利用編譯器的引數型別檢查來排錯,我還希望沒有呼叫開銷時,最適合使用行內函數。

4.6.4.3、巨集定義來實現條件編譯(#define #undef #ifdef)
(1)程式有DEBUG版本和RELEASE版本,區別就是編譯時有無定義DEBUG巨集。

4.6.5.函式的本質
4.6.5.1、C語言為什麼會有函式
(1)整個程式分成多個原始檔,一個檔案分成多個函式,一個函式分成多個語句,這就是整個程式的組織形式。這樣組織的好處在於:分化問題、便於編寫程式、便於分工。
(2)函式的出現是人(程式設計師和架構師)的需要,而不是機器(編譯器、CPU)的需要。
(3)函式的目的就是實現模組化程式設計。說白了就是為了提供程式的可移植性。

4.6.5.2、函式書寫的一般原則:
第一:遵循一定格式。函式的返回型別、函式名、引數列表等。
第二:一個函式只做一件事:函式不能太長也不宜太短,原則是一個函式只做一件事情。
第三:傳參不宜過多:在ARM體系下,傳參不宜超過4個。如果傳參確實需要多則考慮結構體打包
第四:儘量少碰全域性變數:函式最好用傳參返回值來和外部交換資料,不要用全域性變數。

4.6.5.3、函式是動詞、變數是名詞(面相物件中分別叫方法和成員變數)
(1)函式將來被編譯成可執行程式碼段,變數(主要指全域性變數)經過編譯後變成資料或者在執行時變成資料。一個程式的執行需要程式碼和資料兩方向的結合才能完成。
(2)程式碼和資料需要彼此配合,程式碼是為了加工資料,資料必須藉助程式碼來起作用。拿現實中的工廠來比喻:資料是原材料,程式碼是加工流水線。名詞性的資料必須經過動詞性的加工才能變成最終我們需要的產出的資料。這個加工的過程就是程式的執行過程。

4.6.5.4、函式的實質是:資料處理器
(1)程式的主體是資料,也就是說程式執行的主要目標是生成目標資料,我們寫程式碼也是為了目標資料。我們如何得到目標資料?必須2個因素:原材料+加工演算法。原材料就是程式的輸入資料,加工演算法就是程式。
(2)程式的編寫和執行就是為了把原資料加工成目標資料,所以程式的實質就是一個數據處理器。
(3)函式就是程式的一個縮影,函式的引數列表其實就是為了給函式輸入原材料資料,函式的返回值和輸出型引數就是為了向外部輸出目標資料,函式的函式體裡的那些程式碼就是加工演算法。
(4)函式在靜止沒有執行(乖乖的躺在硬盤裡)的時候就好象一臺沒有開動的機器,此時只佔一些儲存空間但是並不佔用資源(CPU+記憶體);函式的每一次執行就好象機器的每一次開機執行,執行時需要耗費資源(CPU+記憶體),執行時可以對資料加工生成目標資料;函式執行完畢會釋放佔用的資源。
(5)整個程式的執行其實就是很多個函式相繼執行的連續過程。

4.6.6.函式的基本使用
4.6.6.1、函式三要素:定義、宣告、呼叫
(1)函式的定義就是函式體、函式宣告是函式原型、函式呼叫就是使用函式
(2)函式定義是函式的根本,函式定義中的函式名錶示了這個函式在記憶體中的首地址,所以可以用函式名來呼叫執行這個函式(實質是指標解引用訪問);函式定義中的函式體是函式的執行關鍵,函式將來執行時主要就是執行函式體。所以一個函式沒有定義就是無基之塔。
(3)函式宣告的主要作用是告訴編譯器函式的原型
(4)函式呼叫就是呼叫執行一個函式。

4.6.6.2、函式原型和作用
(1)函式原型就是函式的宣告,說白了就是函式的函式名、返回值型別、引數列表。
(2)函式原型的主要作用就是給編譯器提供原型,讓編譯器在編譯程式時幫我們進行引數的靜態型別檢查
(3)必須明白:編譯器在編譯程式時是以單個原始檔為單位的(所以一定要在哪裡呼叫在哪裡宣告),而且編譯器工作時已經經過預處理處理了,最最重要的是編譯器編譯檔案時是按照檔案中語句的先後順序執行的。
(4)編譯器從原始檔的第一行開始編譯,遇到函式宣告時就會收到編譯器的函式宣告表中,然後繼續向後。當遇到一個函式呼叫時,就在我的本檔案的函式宣告表中去查這個函式,看有沒有原型相對應的一個函式(這個相對應的函式有且只能有一個)。如果沒有或者只有部分匹配則會報錯或報警告;如果發現多個則會報錯或報警告(函式重複了,C語言中不允許2個函式原型完全一樣,這個過程其實是在編譯器遇到函式定義時完成的。所以函式可以重複宣告但是不能重複定義)

4.6.6.3、函式傳參

4.6.7.遞迴函式
4.6.7.1、什麼是遞迴函式
(1)遞迴函式就是函式中呼叫了自己本身這個函式的函式。
(2)遞迴函式和迴圈的區別。遞迴不等於迴圈
(3)遞迴函式解決問題的典型就是:求階乘、求斐波那契數列

4.6.7.2、函式的遞迴呼叫原理
(1)實際上遞迴函式是在棧記憶體上遞迴執行的,每次遞迴執行一次就需要耗費一些棧記憶體。
(2)棧記憶體的大小是限制遞迴深度的重要因素。

4.6.7.3、使用遞迴函式的原則:收斂性、棧溢位
(1)收斂性就是說:遞迴函式必須有一個終止遞迴的條件。當每次這個函式被執行時,我們判斷一個條件決定是否繼續遞迴,這個條件最終必須能夠被滿足。如果沒有遞迴終止條件或者這個條件永遠不能被滿足,則這個遞迴沒有收斂性,這個遞迴最終要失敗。
(2)因為遞迴是佔用棧記憶體的,每次遞迴呼叫都會消耗一些棧記憶體。因此必須在棧記憶體耗盡之前遞迴收斂(終止),否則就會棧溢位。
(3)遞迴函式的使用是有一定風險的,必須把握好。

4.6.8.函式庫
4.6.8.1、什麼是函式庫?
(1)函式庫就是一些事先寫好的函式的集合,給別人複用。
(2)函式是模組化的,因此可以被複用。我們寫好了一個函式,可以被反覆使用。也可以A寫好了一個函式然後共享出來,當B有相同的需求時就不需自己寫直接用A寫好的這個函式即可。

4.6.8.2、函式庫的由來
(1)最開始是沒有函式庫,每個人寫程式都要從零開始自己寫。時間長了慢慢的早期的程式設計師就積累下來了一些有用的函式。
(2)早期的程式設計師經常參加行業聚會,在聚會上大家互相交換各自的函式庫。
(3)後來程式設計師中的一些大神就提出把大家各自的函式庫收攏在一起,然後經過校準和整理,最後形成了一份標準化的函式庫,就是現在的標準的函式庫,譬如說glibc。

4.6.8.3、函式庫的提供形式:動態連結庫與靜態連結庫
(1)早期的函式共享都是以原始碼的形式進行的。這種方式共享是最徹底的(後來這種原始碼共享的方向就形成了我們現在的開源社群)。但是這種方式有它的缺點,缺點就是無法以商業化形式來發布函式庫。
(2)商業公司需要將自己的有用的函式庫共享給被人(當然是付費的),但是又不能給客戶原始碼。這時候的解決方案就是以庫(主要有2種:靜態庫和動態庫)的形式來提供。
(3)比較早出現的是靜態連結庫。靜態庫其實就是商業公司將自己的函式庫原始碼經過只編譯不連線形成.o的目標檔案,然後用ar工具將.o檔案歸檔成.a的歸檔檔案(.a的歸檔檔案又叫靜態連結庫檔案)。商業公司通過釋出.a庫檔案和.h標頭檔案來提供靜態庫給客戶使用;客戶拿到.a和.h檔案後,通過.h標頭檔案得知庫中的庫函式的原型,然後在自己的.c檔案中直接呼叫這些庫檔案,在連線的時候連結器會去.a檔案中拿出被呼叫的那個函式的編譯後的.o二進位制程式碼段連結進去形成最終的可執行程式。
(4)動態連結庫比靜態連結庫出現的晚一些,效率更高一些,是改進型的。現在我們一般都是使用動態庫。靜態庫在使用者連結自己的可執行程式時就已經把呼叫的庫中的函式的程式碼段連結進最終可執行程式中了,這樣好處是可以執行,壞處是太佔地方了。尤其是有多個應用程式都使用了這個庫函式時,實際上在多個應用程式最後生成的可執行程式中都各自有一份這個庫函式的程式碼段。當這些應用程式同時在記憶體中執行時,實際上在記憶體中有多個這個庫函式的程式碼段,這完全重複了。而動態連結庫本身不將庫函式的程式碼段連結入可執行程式,只是做個標記。然後當應用程式在記憶體中執行時,執行時環境發現它呼叫了一個動態庫中的庫函式時,會去載入這個動態庫到記憶體中,然後以後不管有多少個應用程式去呼叫這個庫中的函式都會跳轉到第一次載入的地方去執行(不會重複載入)。

4.6.8.4、函式庫中庫函式的使用
(1)gcc中編譯連結程式預設是使用動態庫的,要想靜態連結需要顯式用-static來強制靜態連結。
(2)庫函式的使用需要注意3點:第一,包含相應的標頭檔案;第二,呼叫庫函式時注意函式原型;第三,有些庫函式連結時需要額外用-lxxx來指定連結;第四,如果是動態庫,要注意-L指定動態庫的地址。

4.6.9.字串函式
4.6.9.1、什麼是字串
(1)字串就是由多個字元在記憶體中連續分佈組成的字元結構。字串的特點是指定了開頭(字串的指標)和結尾(結尾固定為字元’\0’),而沒有指定長度(長度由開頭地址和結尾地址相減得到)

4.6.9.2、為什麼要講字串處理函式
(1)函式庫為什麼要包含字串處理函式?因為字串處理的需求是客觀的,所以從很早開始人們就在寫很多關於字串處理的函式,然後逐漸形成了現在的字串處理函式庫。
(2)面試筆試時,常用字串處理函式也是經常考到的點。

4.6.9.3、常用字串處理函式
(1)C庫中字串處理函式包含在string.h中,這個檔案在ubuntu系統中在/usr/include中
(2)常見字串處理函式及作用:
memcpy 確定src和dst不會overlap,則使用memcpy效率高
memmove 確定會overlap或者不確定但是有可能overlap,則使用memove比較保險
memset
memcmp
memchr
strcpy
strncpy
strcat
strncat
strcmp
strncmp
strdup
strndup
strchr
strstr
strtok
·······

4.6.10.數學庫函式
4.6.10.1、math.h
(1)真正的數學運算的函式定義在:/usr/include/i386-linux-gnu/bits/mathcalls.h
(2)使用數學庫函式的時候,只需要包含math.h即可。
4.6.10.2、計算開平方
(1)庫函式: double sqrt(double x);

注意區分編譯時警告/錯誤,和連結時的錯誤:
編譯時警告/錯誤:
4.6.10.math.c:9:13: warning: incompatible implicit declaration of built-in function ‘sqrt’ [enabled by default]
double b = sqrt(a);
連結時錯誤:
4.6.10.math.c:(.text+0x1b): undefined reference to `sqrt’
collect2: error: ld returned 1 exit status

分析;這個連結錯誤的意思是:sqrt函式有宣告(宣告就在math.h中)有引用(在math.c)但是沒有定義,連結器找不到函式體。sqrt本來是庫函式,在編譯器庫中是有.a和.so連結庫的(函式體在連結庫中的)。
C連結器的工作特點:因為庫函式有很多,連結器去庫函式目錄搜尋的時間比較久。為了提升速度想了一個折中的方案:連結器只是預設的尋找幾個最常用的庫,如果是一些不常用的庫中的函式被呼叫,需要程式設計師在連結時明確給出要擴充套件查詢的庫的名字。連結時可以用-lxxx來指示連結器去到libxxx.so中去查詢這個函式。

4.6.10.3、連結時加-lm
(1)-lm就是告訴連結器到libm中去查詢用到的函式。
(2)實戰中發現在高版本的gcc中,經常會出現每加-lm也可以編譯連結的。

4.6.11.自己製作靜態連結庫並使用
(1)第一步:自己製作靜態連結庫
首先使用gcc -c只編譯不連線,生成.o檔案;然後使用ar工具進行打包成.a歸檔檔案
庫名不能隨便亂起,一般是lib+庫名稱,字尾名是.a表示是一個歸檔檔案
注意:製作出來了靜態庫之後,釋出時需要釋出.a檔案和.h檔案。

(2)第二步:使用靜態連結庫
把.a和.h都放在我引用的資料夾下,然後在.c檔案中包含庫的.h,然後直接使用庫函式。
第一次,編譯方法:gcc test.c -o test
報錯資訊:test.c:(.text+0xa): undefined reference to func1' test.c:(.text+0x1e): undefined reference tofunc2’
第二次,編譯方法:gcc test.c -o test -laston
報錯資訊:/usr/bin/ld: cannot find -laston
collect2: error: ld returned 1 exit status
第三次,編譯方法:gcc test.c -o test -laston -L.
無報錯,生成test,執行正確。

(3)除了ar名另外,還有個nm命令也很有用,它可以用來檢視一個.a檔案中都有哪些符號

4.6.12.自己製作動態連結庫並使用
(1)動態連結庫的字尾名是.so(對應windows系統中的dll),靜態庫的副檔名是.a
(2)第一步:建立一個動態連結庫。
gcc aston.c -o aston.o -c -fPIC
gcc -o libaston.so aston.o -shared
-fPIC是位置無關碼,-shared是按照共享庫的方式來連結。
注意:做庫的人給用庫的人釋出庫時,釋出libxxx.so和xxx.h即可。
(3)第二步:使用自己建立的共享庫。
第一步,編譯方法:gcc test.c -o test
報錯資訊:test.c:(.text+0xa): undefined reference to func1' test.c:(.text+0x1e): undefined reference tofunc2’
collect2: error: ld returned 1 exit status

第二步,編譯方法:gcc test.c -o test -laston
報錯資訊:/usr/bin/ld: cannot find -laston
collect2: error: ld returned 1 exit status

第三步,編譯方法:gcc test.c -o test -laston -L.
編譯成功

但是執行出錯,報錯資訊:
error while loading shared libraries: libaston.so: cannot open shared object file: No such file or directory

錯誤原因:動態連結庫執行時需要被載入(執行時環境在執行test程式的時候發現他動態連結了libaston.so於是乎會去固定目錄嘗試載入libaston.so,如果載入失敗則會列印以上錯誤資訊。)

解決方法一:
將libaston.so放到固定目錄下就可以了,這個固定目錄一般是/usr/lib目錄。
cp libaston.so /usr/lib即可

解決方法二:使用環境變數LD_LIBRARY_PATH。作業系統在載入固定目錄/usr/lib之前,會先去LD_LIBRARY_PATH這個環境變數所指定的目錄下去尋找,如果找到就不用去/usr/lib下面找了,如果沒找到再去/usr/lib下面找。所以解決方案就是將libaston.so所在的目錄匯出到環境變數LD_LIBRARY_PATH中即可。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/mnt/hgfs/Winshare/s5pv210/AdvancedC/4.6.PreprocessFunction/4.6.12.sharedobject.c/sotest

在ubuntu中還有個解決方案三,用ldconfig

(4)ldd命令:作用是可以在一個使用了共享庫的程式執行之前解析出這個程式使用了哪些共享庫,並且檢視這些共享庫是否能被找到,能被解析(決定這個程式是否能正確執行)。