1.C語(yǔ)言預(yù)處理理論
1.1边败、由源碼到可執(zhí)行程序的過(guò)程
(1)源碼.c→(預(yù)處理)→預(yù)處理過(guò)的.i源文件→(編譯)→匯編文件.s→(匯編)→目標(biāo)文件.o→(鏈接)→elf可執(zhí)行程序
(2)預(yù)處理用預(yù)處理器慈缔,編譯用編譯器叮称,匯編用匯編器,鏈接用鏈接器藐鹤,這幾個(gè)工具再加上其他一些額外的會(huì)用到的可用工具瓤檐,合起來(lái)叫編譯工具鏈。gcc就是一個(gè)編譯工具鏈娱节。
1.2挠蛉、預(yù)處理的意義
(1)編譯器本身的主要目的是編譯源代碼,將c的源代碼轉(zhuǎn)換成.s的匯編代碼肄满。編譯器聚集核心功能后谴古,就剝離出了一些非核心的功能到預(yù)處理了。
(2)預(yù)處理器幫編譯器做了一些編譯前的雜事稠歉。
1.3掰担、gcc中只預(yù)處理不編譯的方法
(1)gcc -E xx.c -o xx.i
可以實(shí)現(xiàn)只預(yù)處理不編譯,一般情況下沒(méi)必要只預(yù)處理不編譯怒炸,但有時(shí)候這種技巧可以用來(lái)幫助我們研究預(yù)處理過(guò)程带饱,幫助debug程序。
1.4阅羹、總結(jié):宏定義被預(yù)處理時(shí)的現(xiàn)象
(1)第一纠炮,宏定義語(yǔ)句本身不見(jiàn)了(可見(jiàn)編譯器根本就不認(rèn)識(shí)#define月趟,編譯器根本不知道還有個(gè)宏定義)。
(2)第二恢口,typedef重命名語(yǔ)句還在孝宗,說(shuō)明它和宏定義是有本質(zhì)區(qū)別的(說(shuō)明typedef是由編譯器來(lái)處理而不是預(yù)處理的)。
#define pchar char *
typedef char * PCHAR
int main(void)
{
pchar p1, p2;
return 0;
}
預(yù)處理:gcc -E xx.c -o xx.i
然后打開(kāi)xx.i文件查看耕肩,發(fā)現(xiàn)宏定義都被替換
2.C語(yǔ)言預(yù)處理代碼實(shí)戰(zhàn)
2.1因妇、頭文件包含
(1)#include <>和#include ""的區(qū)別:<>專門用來(lái)包含系統(tǒng)提供的頭文件(就是系統(tǒng)自帶的,不是程序員自己寫的)猿诸,""用來(lái)包含自己寫的頭文件婚被;更深層次來(lái)說(shuō):<>的話C語(yǔ)言編譯器只會(huì)到系統(tǒng)指定目錄(編譯器配置的或者操作系統(tǒng)配置的尋找目錄,比如在ubuntu中是/usr/include目錄梳虽,編譯器還允許用-I來(lái)附加指定其他的包含路徑)去尋找這個(gè)頭文件(隱含的意思就是不會(huì)找當(dāng)前目錄下)址芯,如果找不到就會(huì)提示這個(gè)頭文件不存在。
(2)""包含的頭文件窜觉,編譯器默認(rèn)會(huì)先在當(dāng)前目錄下尋找相應(yīng)的頭文件谷炸,如果沒(méi)找到然后再到系統(tǒng)指定目錄去尋找,如果還沒(méi)找到則提示文件不存在禀挫。
總結(jié):規(guī)則雖然允許用雙引號(hào)來(lái)包含系統(tǒng)指定的目錄旬陡,但是一般的使用原則是:如果是系統(tǒng)指定的自帶的用<>;如果是自己寫的在當(dāng)前目錄下放著用""语婴,如果是自己寫的但是集中放在了一起轉(zhuǎn)么內(nèi)存放頭文件的目錄下將來(lái)在編譯器中用-I參數(shù)來(lái)尋找描孟,這種情況下用<>。
(3)頭文件包含的真實(shí)含義就是:在#include <xx.h>
的那一行砰左,將xx.h這個(gè)頭文件的內(nèi)容原地展開(kāi)替換這一行#include語(yǔ)句匿醒。過(guò)程在預(yù)處理中進(jìn)行。
2.2缠导、注釋
(1)注釋是給人看的青抛,不是給編譯器看的。
(2)編譯器既然不看注釋酬核,那么編譯時(shí)最好沒(méi)有注釋。實(shí)際上在預(yù)處理階段适室,預(yù)處理器會(huì)拿掉程序中所有注釋語(yǔ)句嫡意,到了編譯器階段程序中其實(shí)已經(jīng)沒(méi)有注釋了。
2.3捣辆、條件編譯
(1)有時(shí)候我們希望程序有多種配置蔬螟,我們?cè)谠创a編寫時(shí)寫好各種配置的代碼,然后給個(gè)配置開(kāi)關(guān)汽畴,在源代碼級(jí)別去修改配置開(kāi)關(guān)來(lái)讓程序編譯出不同的效果旧巾。
(2)條件編譯中用的兩種條件判定方法分別是#ifdef和#if耸序,它們的區(qū)別是#ifdef xxx判定條件成立與否時(shí)主要是看xxx這個(gè)符號(hào)在本語(yǔ)句之前有沒(méi)有被定義,只要定義了(我們可以直接#define XXX或者#define XXX 12或者#define XXX YYY)這個(gè)符號(hào)就是成立的鲁猩。
格式:#if(條件表達(dá)式)
它的判定標(biāo)準(zhǔn)是()中的表達(dá)式是否為true還是false坎怪,跟C中的if語(yǔ)句有點(diǎn)像。
3.宏定義
3.1廓握、宏定義的規(guī)則和使用解析
(1)宏定義的解析規(guī)則就是:在預(yù)處理階段由預(yù)處理器進(jìn)行替換搅窿,這個(gè)替換是原封不動(dòng)的替換。
(2)宏定義替換會(huì)遞歸進(jìn)行隙券,直到替換出來(lái)的值本身不再是一個(gè)宏為止男应。
(3)一個(gè)正確的宏定義式子本身分為3部分:第一部分是#define,第二部分是宏名娱仔,剩下的宏體為第三部分沐飘。
(4)宏可以帶參數(shù),稱為帶參宏牲迫。帶參宏的使用和帶參函數(shù)非常像耐朴,但是使用上有一些差異。在定義帶參宏時(shí)恩溅,每一個(gè)參數(shù)在宏體中引用時(shí)都必須加括號(hào)隔箍,最后整體再加括號(hào),括號(hào)缺一不可脚乡。
3.2蜒滩、宏定義示例1:MAX宏
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
int main (void)
{
int x =3, y =4;
int max = MAX(2+x, y);
printf("max = %d.\n", max);
return 0;
}
關(guān)鍵:三目運(yùn)算符,括號(hào)使用
3.3奶稠、宏定義示例2:SEC_PER_YEAR
用宏定義表示一年有多少秒
#define SEC_PER_YEAR (365*24*60*60UL)
int main(void)
{
unsigned int l = SEC_PER_YEAR;
printf("l = %u.\n", l);
return 0;
}
關(guān)鍵:
①第一點(diǎn):當(dāng)一個(gè)數(shù)字直接出現(xiàn)在程序時(shí)俯艰,它的類型默認(rèn)是int。所以用U
②第二點(diǎn):一年有多少秒锌订,這個(gè)數(shù)字剛好超過(guò)了int類型存儲(chǔ)的范圍竹握。所以用L
3.4、帶參宏和帶參函數(shù)的區(qū)別(宏定義的缺陷)
(1)宏定義是在預(yù)處理期間處理的辆飘,而函數(shù)是在編譯期間處理的啦辐。這個(gè)區(qū)別帶來(lái)的實(shí)質(zhì)差異是:宏定義最終是在調(diào)用宏的地方把宏體原地展開(kāi),而函數(shù)是在調(diào)用函數(shù)處跳轉(zhuǎn)到函數(shù)中去執(zhí)行蜈项,執(zhí)行完后再跳轉(zhuǎn)回來(lái)芹关。
注意:宏定義和函數(shù)的最大差別就是
宏定義是原地展開(kāi),因此沒(méi)有調(diào)用開(kāi)銷紧卒;而函數(shù)是跳轉(zhuǎn)執(zhí)行再返回侥衬,因此函數(shù)有比較大的調(diào)用開(kāi)銷。所以宏定義和函數(shù)相比,優(yōu)勢(shì)就是沒(méi)有調(diào)用開(kāi)銷轴总,沒(méi)有傳參開(kāi)銷直颅。所以當(dāng)函數(shù)體很短(尤其是一句話時(shí)),可以用宏定義來(lái)代替怀樟,這樣效率高功偿。
(2)帶參宏和帶參函數(shù)的一個(gè)重要差別是:宏定義不檢查參數(shù)的類型,返回值也不會(huì)附帶類型漂佩;而函數(shù)有明確的參數(shù)類型和返回值類型脖含。當(dāng)我們調(diào)用函數(shù)時(shí)編譯器會(huì)幫我們做參數(shù)的靜態(tài)類型檢查,如果編譯器發(fā)現(xiàn)我們實(shí)際傳參和參數(shù)聲明不同時(shí)會(huì)報(bào)警告或錯(cuò)誤投蝉。
注意:用函數(shù)的時(shí)候程序員不太用操心類型不匹配养葵,因?yàn)榫幾g器會(huì)檢查,如果不匹配編譯器會(huì)提示瘩缆;用宏定義的時(shí)候程序員必須很注意實(shí)際傳參和宏所希望的參數(shù)類型要一致关拒,否則可能編譯不報(bào)錯(cuò)但是運(yùn)行有誤。
3.5庸娱、內(nèi)聯(lián)函數(shù)和inline關(guān)鍵字
(1)內(nèi)聯(lián)函數(shù)通過(guò)在函數(shù)定義前加inline關(guān)鍵字實(shí)現(xiàn)着绊。
(2)內(nèi)聯(lián)函數(shù)本質(zhì)上是函數(shù),所以有函數(shù)的優(yōu)點(diǎn)(內(nèi)聯(lián)函數(shù)和是編譯負(fù)責(zé)處理的熟尉,編譯器可以幫我們做參數(shù)的靜態(tài)類型檢查)归露;但是他同時(shí)也有帶參宏的優(yōu)點(diǎn)(不用調(diào)用開(kāi)銷,而是原地展開(kāi))斤儿。所以幾乎可以這樣認(rèn)為:內(nèi)聯(lián)函數(shù)就是帶了參數(shù)靜態(tài)類型檢查的宏剧包。
(3)當(dāng)我們的函數(shù)內(nèi)的函數(shù)體很短(比如只有一兩句)的時(shí)候,我們又希望利用編譯器的參數(shù)類型檢查來(lái)排錯(cuò)往果,我還希望沒(méi)有調(diào)用開(kāi)銷時(shí)疆液,最適合使用內(nèi)聯(lián)函數(shù)。
3.6陕贮、宏定義來(lái)實(shí)現(xiàn)條件編譯(#define 堕油、#undef、#ifdef)
程序有DEBUG版本和RELEASE版本肮之,區(qū)別就是編譯時(shí)有無(wú)DEBUG宏掉缺。
#define DEBUG
#undef DEBUG // 注銷一個(gè)宏,如果前面有宏定義這個(gè)宏則取消這個(gè)宏
#ifdef DEBUG
#define debug(x) printf(x)
#else
#define debug(x)
#endif
4.遞歸函數(shù)
4.1戈擒、什么是遞歸函數(shù)
(1)遞歸函數(shù)就是函數(shù)中調(diào)用了自己本身這個(gè)函數(shù)的函數(shù)眶明。
int jiecheng(int n)
{
// 傳參錯(cuò)誤檢驗(yàn)
if (n < 1)
{
printf("n必須大于或等于1.\n");
return -1;
}
if (n == 1)
{
return -1;
}
else
{
// 一直往里走,直到最后執(zhí)行return才一層層出來(lái)
return (n*jiecheng(n-1));
}
}
(2)遞歸函數(shù)和循環(huán)的區(qū)別峦甩。遞歸不等于循環(huán);
(3)遞歸函數(shù)解決的經(jīng)典問(wèn)題:求階乘、斐波那契數(shù)列
4.2凯傲、函數(shù)的遞歸調(diào)用原理
(1)實(shí)際上遞歸函數(shù)是在棧內(nèi)存上遞歸執(zhí)行的犬辰,每次遞歸執(zhí)行一次就需要耗費(fèi)一些棧內(nèi)存。
(2)棧內(nèi)存的大小是限制遞歸深度的重要因素冰单。
4.3幌缝、使用遞歸函數(shù)的原則:收斂性、棧溢出
(1)收斂性就是說(shuō):遞歸函數(shù)必須有一個(gè)終止遞歸的條件诫欠。當(dāng)每次這個(gè)函數(shù)被執(zhí)行時(shí)涵卵,我們判斷一個(gè)條件決定是否遞歸,這個(gè)條件最終必須能夠被滿足荒叼。如果沒(méi)有遞歸終止條件或者這個(gè)條件永遠(yuǎn)不能被滿足轿偎,則這個(gè)遞歸沒(méi)有收斂性,這個(gè)遞歸最終要失敗被廓。
(2)因?yàn)檫f歸是占用棧內(nèi)存的坏晦,每次遞歸調(diào)用都會(huì)消耗一些棧內(nèi)存。因此必須在棧內(nèi)存消耗之前遞歸收斂(終止)嫁乘,否則會(huì)棧溢出昆婿,段錯(cuò)誤。
(3)遞歸函數(shù)的使用時(shí)有風(fēng)險(xiǎn)的蜓斧。
void digui(int n)
{
int a[100];
if (n >1)
{
digui(n-1);
}
else
{
printf("結(jié)束遞歸,n = %d.\n", n);
}
printf("遞歸后:n = %d.\n", n);
}
5.函數(shù)庫(kù)
5.1仓蛆、什么是函數(shù)庫(kù)
(1)函數(shù)庫(kù)就是一些事先寫好的函數(shù)的集合,給別人復(fù)用挎春。
(2)函數(shù)是模塊化的看疙,因此可以被復(fù)用。我們寫好了一個(gè)函數(shù)搂蜓,可以被反復(fù)使用狼荞。也可以A寫好了一個(gè)函數(shù)然后共享出來(lái),當(dāng)b有相同的需求時(shí)就不需要自己寫帮碰,直接用A寫好的這個(gè)函數(shù)即可相味。
5.2、函數(shù)庫(kù)的由來(lái)
(1)最開(kāi)始是沒(méi)有函數(shù)庫(kù)殉挽,每個(gè)人寫程序都要從零開(kāi)始自己寫丰涉。時(shí)間長(zhǎng)了慢慢的,早起程序員就積累下來(lái)了一些有用的函數(shù)斯碌。
(2)后來(lái)程序員中的一些大神就提出把大家各自的函數(shù)庫(kù)收攏在一起一死,然后經(jīng)過(guò)校準(zhǔn)和管理,最后形成了一份標(biāo)準(zhǔn)化的函數(shù)庫(kù)傻唾,就是現(xiàn)在的標(biāo)準(zhǔn)函數(shù)庫(kù)投慈,比如glibc承耿。
5.3、函數(shù)庫(kù)的提供形式:動(dòng)態(tài)鏈接庫(kù)與靜態(tài)鏈接庫(kù)
(1)早起的函數(shù)共享都是以源代碼的形式進(jìn)行的伪煤。這種方式共享是最徹底的(后來(lái)這種源碼共享的方式就形成了我們現(xiàn)在的開(kāi)源社區(qū))加袋。但是這種方式有它的缺點(diǎn),缺點(diǎn)是無(wú)法以商業(yè)化形式來(lái)發(fā)布函數(shù)庫(kù)抱既。
(2)商業(yè)公司需要將自己的有用函數(shù)庫(kù)共享給別人(當(dāng)然是付費(fèi)的)职烧,但是又不能給客戶源代碼。這時(shí)候的解決方案就是以庫(kù)(主要有2種:靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù))的形式來(lái)提供防泵。
(3)比較早出現(xiàn)的是靜態(tài)鏈接庫(kù)蚀之。靜態(tài)庫(kù)其實(shí)就是商業(yè)公司將自己的函數(shù)庫(kù)源代碼經(jīng)過(guò)只編譯不鏈接形成.o的目標(biāo)文件,然后用ar工具將.o文件歸檔為.a的歸檔文件(.a的歸檔文件又叫靜態(tài)鏈接庫(kù)文件)捷泞。商業(yè)公司通過(guò)發(fā)布.a庫(kù)文件和.h頭文件來(lái)提供靜態(tài)庫(kù)給客戶使用足删。客戶拿到.a和.h文件后肚邢,通過(guò).h文件得知庫(kù)中的庫(kù)函數(shù)的原型(就是函數(shù)名壹堰,參數(shù)列表,返回值類型)骡湖,然后在自己的.c文件中直接調(diào)用這些庫(kù)文件贱纠,在鏈接的時(shí)候鏈接器會(huì)去.a文件中拿出被調(diào)用的那個(gè)函數(shù)的編譯后的.o二進(jìn)制代碼段鏈接進(jìn)去形成最終的可執(zhí)行程序。
(4)動(dòng)態(tài)鏈接庫(kù)比靜態(tài)鏈接庫(kù)出現(xiàn)的晚一些响蕴,效率更高一些谆焊,是改進(jìn)型的。現(xiàn)在我們一般都是使用動(dòng)態(tài)庫(kù)浦夷。靜態(tài)庫(kù)在用戶鏈接自己的可執(zhí)行程序時(shí)就已經(jīng)把調(diào)用的庫(kù)中的函數(shù)的代碼段鏈接進(jìn)最終可執(zhí)行程序中了辖试,這樣好處是可以執(zhí)行,壞處是太占地方了劈狐。
尤其是有多個(gè)應(yīng)用程序都是用了這個(gè)庫(kù)函數(shù)時(shí)罐孝,實(shí)際上在多個(gè)應(yīng)用程序最后生成的可執(zhí)行程序中都各自有一份這個(gè)庫(kù)函數(shù)的代碼段。當(dāng)這些應(yīng)用程序同時(shí)在內(nèi)存中運(yùn)行時(shí)肥缔,實(shí)際上在內(nèi)存中有對(duì)個(gè)這個(gè)庫(kù)函數(shù)的代碼莲兢,這完全重復(fù)了。而動(dòng)態(tài)鏈接庫(kù)本身不將庫(kù)函數(shù)的代碼段鏈接入可執(zhí)行程序续膳,只是做個(gè)標(biāo)記改艇。然后當(dāng)應(yīng)用程序在內(nèi)存中執(zhí)行時(shí),運(yùn)行時(shí)環(huán)境發(fā)現(xiàn)它調(diào)用了一個(gè)動(dòng)態(tài)庫(kù)中的庫(kù)函數(shù)時(shí)坟岔,會(huì)去加載這個(gè)動(dòng)態(tài)庫(kù)的函數(shù)到內(nèi)存中谒兄,然后以后不管有多少個(gè)應(yīng)用程序去調(diào)用這個(gè)庫(kù)中的函數(shù)都會(huì)跳轉(zhuǎn)到第一次加載的地方去執(zhí)行(不會(huì)重復(fù)加載)妄壶。
舉例:
int main(void)
{
printf("hello world.\n");
return 0;
}
編譯對(duì)比:
①動(dòng)態(tài)鏈接庫(kù):gcc xx.c
②靜態(tài)鏈接庫(kù):gcc xx.c -static
5.4览绿、函數(shù)庫(kù)中函數(shù)的使用
(1)gcc中編譯鏈接程序默認(rèn)是使用動(dòng)態(tài)庫(kù)的,要像靜態(tài)鏈接需要顯式用"-static"來(lái)強(qiáng)制靜態(tài)鏈接捻脖。
(2)庫(kù)函數(shù)的使用需要注意3點(diǎn):
①第一豪筝,包含相應(yīng)的頭文件俊犯;
②第二舔琅,調(diào)用庫(kù)函數(shù)時(shí)注意函數(shù)原型碗硬;
③第三,有些庫(kù)函數(shù)鏈接時(shí)需要額外用"-lxx"來(lái)指定鏈接绵咱;
④第四,如果是動(dòng)態(tài)庫(kù)熙兔,要注意"-L"指定動(dòng)態(tài)庫(kù)的地址悲伶。
6.字符串處理函數(shù)
6.1、什么是字符串
字符串就是由多個(gè)字符在內(nèi)存中連續(xù)分布組成的字符結(jié)構(gòu)住涉。字符串的特點(diǎn)是指定了開(kāi)頭(字符串的指針)和結(jié)尾(結(jié)尾固定字符為'\0')麸锉,而沒(méi)有指定長(zhǎng)度(長(zhǎng)度由開(kāi)頭地址和結(jié)尾地址相減得到)。
6.2舆声、為什么要講字符串處理函數(shù)
(1)函數(shù)庫(kù)為什么要包含字符串處理函數(shù)花沉?因?yàn)樽址幚淼男枨髸r(shí)客觀的,所以從很早開(kāi)始人們就在寫很多關(guān)于字符串處理的函數(shù)媳握,然后逐漸形成了現(xiàn)在的字符串處理函數(shù)庫(kù)碱屁。
(2)面試筆試時(shí),常用字符串處理函數(shù)也是經(jīng)扯暾遥考到的娩脾。
6.3、常用字符串處理函數(shù)
(1)C庫(kù)中字符串處理函數(shù)包含在string.h中打毛,這個(gè)文件在ubuntu系統(tǒng)中在/usr/include中柿赊。
(2)常見(jiàn)字符串處理函數(shù)及作用:
①memcpy:確定src和dst不會(huì)overlap(內(nèi)存重疊),則用memcpy效率高幻枉;
②memmove:不確定overlap碰声,則用memmove更保險(xiǎn)
③還有很多,研究庫(kù)函數(shù)是最好的打基礎(chǔ)方法熬甫,先以4~5個(gè)博客看知識(shí)胰挑,然后總結(jié)。
7.數(shù)學(xué)庫(kù)函數(shù)
7.1罗珍、math.h
(1)真正的數(shù)學(xué)運(yùn)算的函數(shù)定義在:
/usr/include/i386-linux-gnu/bits/mathcalls.h
(2)使用數(shù)學(xué)庫(kù)函數(shù)的時(shí)候洽腺,只需要包含math.h即可。
7.2覆旱、計(jì)算開(kāi)平方
(1)庫(kù)函數(shù):double sqrt(double x);
#include <stdio.h>
#include <math.h>
int main(void)
{
double a = 16.0;
double b = sqrt(a);
printf("b = %lf.\n", b);
return 0;
}
直接編譯報(bào)錯(cuò):
xx.c:(.text+0x1b):undefined reference to 'sqrt' collect2:error:ld returned 1 exit status
看到ld知道這是個(gè)鏈接錯(cuò)誤:sqrt函數(shù)有聲明(聲明在math.h中)有引用但是沒(méi)定義蘸朋,鏈接器找不到函數(shù)體。sqrt本來(lái)是庫(kù)函數(shù)扣唱,在編譯器庫(kù)中是有.a和.so鏈接庫(kù)的(函數(shù)體在鏈接庫(kù)中)藕坯。
(2)C鏈接器的工作特點(diǎn):因?yàn)閹?kù)函數(shù)有很多团南,鏈接器去庫(kù)函數(shù)目錄搜索的時(shí)間比較久。為了提升速度想到了一個(gè)折中的方案:鏈接器只是默認(rèn)的尋找?guī)讉€(gè)最常用的庫(kù)炼彪,如果是一些不常用的庫(kù)中的函數(shù)被調(diào)用吐根,需要程序員在鏈接時(shí)明確給出要擴(kuò)展查找的庫(kù)的名字。鏈接時(shí)可以用"-lxx"來(lái)指示鏈接器去到"libxx.so"中去查找這個(gè)函數(shù)辐马。
示例:
gcc xx.c -lm
// 編譯正常拷橘,鏈接到了math.h庫(kù)函數(shù)
ldd a.out
// 可查找鏈接器鏈接了哪些文件
7.3、鏈接時(shí)加“-lm”
(1)"-lm"就是告訴鏈接器到libm中去查找用到的函數(shù)
(2)實(shí)戰(zhàn)中發(fā)現(xiàn)在高版本的gcc中喜爷,經(jīng)常會(huì)出現(xiàn)沒(méi)加"-lm"也可以編譯鏈接的冗疮。
7.4、自己制作靜態(tài)鏈接庫(kù)并使用
(1)第一步:自己制作靜態(tài)鏈接庫(kù)
首先檩帐,使用gcc -c只編譯不鏈接术幔,生成.o目標(biāo)文件,然后使用ar工具進(jìn)行打包成.a歸檔文件湃密,庫(kù)名不能隨便亂起诅挑,一般是lib+庫(kù)名字,后綴名是.a表示是一個(gè)歸檔文件泛源。
注意:制作出來(lái)了靜態(tài)庫(kù)之后拔妥,發(fā)布時(shí)需要發(fā)布.a文件和.h文件。
(2)第二步:使用靜態(tài)鏈接庫(kù)
把.a和.h都放在我引用的文件夾下达箍,然后在.c文件中包含庫(kù)的.h毒嫡,然后直接使用庫(kù)函數(shù)。
(aston.c)
#include <stdio.h>
void func1()
{
printf("func1 in aston.c.\n");
}
int func(int a, int b)
{
printf("func2 in aston.c.\n");
return a+b;
}
(test.c)
#include <stdio.h>
#include "aston.h"
int main(void)
{
func1();
int b = func2(3, 8)
printf("b = %d.\n", b);
return 0;
}
(aston.h)
void func1(void)
int func2(int a, int b)
(touch Makefile)
all:
gcc aston -o aston.o -c
ar -rc libaston.a aston.o
Makefile是執(zhí)行語(yǔ)句的集合文件幻梯,第一句表示只編譯不鏈接兜畸;libaston.a這個(gè)起名有講究,lib后面的名字必須與包名一致碘梢,也就是上面的aston.c咬摇。
這個(gè)起名可以通過(guò)下面的例子聯(lián)想
math.c → limb → -lm
aston.c → libaston → -laston
編譯嘗試并分析:
①第一次,編譯方法:gcc test.c -o test
報(bào)錯(cuò)信息:test.c:(.text+0xa):undefined reference to 'func1'. test.c:~~
②第二次煞躬,編譯方法:gcc test.c -o test -laston
報(bào)錯(cuò)信息:/usr/bin/ld:cannot find -laston collect2:error:ld return 1exit status
③第三次肛鹏,編譯方法:gcc test.c -o test -laston -L.
無(wú)報(bào)錯(cuò),生成test恩沛,執(zhí)行正確在扰。注意L后面有個(gè)點(diǎn).
(3)除了ar命令以外,還有個(gè)nm命令也很有用雷客,它可以用來(lái)查看一個(gè).a文件中都有哪些符號(hào)芒珠。
7.5、自己制作動(dòng)態(tài)鏈接庫(kù)并使用
(1)動(dòng)態(tài)鏈接庫(kù)的后綴名是.so(對(duì)應(yīng)Windows的ddl)搅裙,靜態(tài)庫(kù)的擴(kuò)展名是.a皱卓。
(2)第一步裹芝。創(chuàng)建一個(gè)動(dòng)態(tài)鏈接庫(kù)
(touch Makefile)
gcc aston.c -o aston.o -c -fPIC
gcc -o libaston.so aston.o -shared
-fPIC是位置無(wú)關(guān)碼
-shared是按照共享庫(kù)的方式來(lái)鏈接的
文件存放:
sotest文件夾內(nèi):aston.h、libaston.so娜汁、test.c
sotest文件夾本級(jí)目錄:aston.c嫂易、aston.h、Makefile
(test.c)
#include <stdio.h>
#include "aston.h"
int main(void)
{
func1();
int b = func2(3, 8)
printf("b = %d.\n", b);
return 0;
}
注意:做庫(kù)的人給用庫(kù)的人發(fā)布時(shí)掐禁,只要發(fā)布libxxx.so和xxx.h即可怜械。
(3)第二步,使用動(dòng)態(tài)鏈接庫(kù)
gcc test.c -o test -laston -L.
編譯成功傅事。但是運(yùn)行報(bào)錯(cuò):error while loading shared libraries:libaston.so:cannot open shared object file:No such file or directory宫盔。
錯(cuò)誤的原因:動(dòng)態(tài)鏈接庫(kù)運(yùn)行時(shí)需要被加載(運(yùn)行時(shí)環(huán)境在執(zhí)行test程序的時(shí)候發(fā)現(xiàn)它動(dòng)態(tài)鏈接libaston.so,于是乎回去固定目錄嘗試加載libaston.so,如果加載失敗則會(huì)打印錯(cuò)誤信息)。
解決方法:
①將libaston.so放到固定目錄下就可以了享完,這個(gè)固定目錄一般是/usr/lib目錄。
cp libaston.so /usr/lib
②(推薦)使用環(huán)境變量LD_LIBRARY_PATH有额。操作系統(tǒng)在加載固定目錄/usr/lib之前般又,會(huì)先去LD_LIBRARY_PATH這個(gè)環(huán)境變量所指定的目錄下去查找,如果找到就不用去/usr/lib下面找了巍佑,如果沒(méi)找到再去/usr/lib目錄尋找茴迁。所以解決方案就是將libaston.so所在的目錄導(dǎo)出到環(huán)境變量LD_LIBRARY_PATH中即可。
export LD_LIBRARY_PATH = $LD_LIBRARY:(pwd查看)
萤衰,無(wú)括號(hào)
③在ubuntu中還有個(gè)解決方法:用ldconfig堕义,具體可以去網(wǎng)上查找。
(4)ldd命令:作用是可以在一個(gè)使用了共享庫(kù)的程序執(zhí)行之前解析出這個(gè)程序使用了哪些共享庫(kù)脆栋,并且查看這些共享庫(kù)是否能被找到倦卖,能被解析(決定這個(gè)程序是否能正確執(zhí)行)。
比如:ldd test
發(fā)現(xiàn)有一個(gè)not found則運(yùn)行后肯定會(huì)報(bào)錯(cuò)椿争。這是一種不運(yùn)行程序也可以查看是否會(huì)報(bào)錯(cuò)的方法怕膛。