日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無(wú)先后,達(dá)者為師

網(wǎng)站首頁(yè) 編程語(yǔ)言 正文

Linux下動(dòng)靜態(tài)庫(kù)的打包與使用指南(C/C++)_C 語(yǔ)言

作者:謎一樣的男人1 ? 更新時(shí)間: 2023-04-08 編程語(yǔ)言

前言

為什么用動(dòng)靜態(tài)庫(kù)

我們?cè)趯?shí)際開(kāi)發(fā)中,經(jīng)常要使用別人已經(jīng)實(shí)現(xiàn)好的功能,這是為了開(kāi)發(fā)效率和魯棒性(健壯性);因?yàn)槟切┕δ芏际琼敿獾墓こ處熞呀?jīng)寫(xiě)好的,并且已經(jīng)踐行多年的代碼。

那么如何使用他人開(kāi)發(fā)的功能呢?

1.庫(kù): 包括靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)。

2.開(kāi)源代碼。

3.基本的網(wǎng)絡(luò)功能調(diào)用,比如各種網(wǎng)絡(luò)接口、語(yǔ)音識(shí)別等等。

這其中,我們將詳細(xì)介紹靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù):

為什么在實(shí)際工作中一般將源碼打包成動(dòng)態(tài)靜態(tài)庫(kù)來(lái)給不同的人使用

  1. 知識(shí)就是財(cái)富,你只是給某些人用一下你寫(xiě)的業(yè)務(wù)功能,并不想暴露源碼的邏輯,那就打包成.obj目標(biāo)文件甩給他用;
  2. 你也可以自己給自己封裝庫(kù),因?yàn)槟阕约壕幾g生成了.obj目標(biāo)文件,是已經(jīng)編譯完成了的,只需要把這個(gè)庫(kù)對(duì)應(yīng)的頭文件引入新的程序中,.obj文件放入庫(kù)路徑,這樣新的程序鏈接器就可以直接把功能鏈接起來(lái),方便部署使用,省去了反復(fù)編譯的麻煩

動(dòng)態(tài)鏈接與靜態(tài)鏈接

一般情況下,為了更好的支持開(kāi)發(fā),第三方庫(kù)或者是語(yǔ)言庫(kù)都必須提供靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)(eg:C C++等官方庫(kù)),這是方便程序員根據(jù)需求功能進(jìn)行可執(zhí)行文件的生成;

動(dòng)態(tài)鏈接使用動(dòng)態(tài)庫(kù),而靜態(tài)鏈接使用靜態(tài)庫(kù)。

一般來(lái)說(shuō),我們gcc編譯默認(rèn)是動(dòng)態(tài)鏈接的而如果加上-static選項(xiàng),那么生成的可執(zhí)行文件將為靜態(tài)生成;

底層優(yōu)缺點(diǎn)

動(dòng)態(tài)鏈接文件信息:

靜態(tài)鏈接文件信息:

可以明顯發(fā)現(xiàn)動(dòng)態(tài)鏈接的文件大小明顯要比靜態(tài)鏈接的文件大小要小多了

這是因?yàn)?strong>動(dòng)態(tài)鏈接是當(dāng)程序執(zhí)行到調(diào)用接口時(shí),編譯器再去特定路徑查找目標(biāo)接口的可執(zhí)行文件,直接進(jìn)行計(jì)算;

靜態(tài)鏈接比較暴力,鏈接時(shí)候直接將目標(biāo)接口的二進(jìn)制代碼全部鏈接到原文件中去,這也就是靜態(tài)鏈接生成的文件這么大的原因了;(畢竟把二進(jìn)制代碼copy過(guò)來(lái)了)

但是這些都是相對(duì)的,有優(yōu)點(diǎn)就有缺點(diǎn):

萬(wàn)一動(dòng)態(tài)庫(kù)路徑中的庫(kù)丟失損壞 ,動(dòng)態(tài)鏈接的程序到目標(biāo)位置了,過(guò)來(lái)用的時(shí)候肯定出錯(cuò)了;

靜態(tài)鏈接因?yàn)榫幾g的時(shí)候吧二進(jìn)制代碼考過(guò)去了,不依賴(lài)原生庫(kù),即便原庫(kù)代碼丟失也沒(méi)事;

小結(jié)

Linux下的動(dòng)靜態(tài)庫(kù)

linux下庫(kù)的命名格式一般為:

靜態(tài)庫(kù): lib+庫(kù)的名字+.a eg:c標(biāo)準(zhǔn)庫(kù)為 libc.a

動(dòng)態(tài)庫(kù): lib+庫(kù)的名字+.so

靜態(tài)庫(kù)是指程序在編譯鏈接的時(shí)候把庫(kù)的二進(jìn)制可執(zhí)行代碼鏈接到可執(zhí)行文件中。程序運(yùn)行的時(shí)候?qū)⒉辉傩枰o態(tài)庫(kù)。

而動(dòng)態(tài)庫(kù)則是指程序在運(yùn)行的時(shí)候才去啟動(dòng)指定位置的動(dòng)態(tài)庫(kù)的代碼,使其加載到內(nèi)存共享區(qū)中,多個(gè)程序共享使用庫(kù)的二進(jìn)制代碼, 不用拉到本文件中來(lái)。

  • 一個(gè)與動(dòng)態(tài)庫(kù)鏈接的可執(zhí)行文件僅僅包含它用到的函數(shù)入口地址的一個(gè)表(頭文件),而不是外部函數(shù)所在目標(biāo)文件(.o)的整個(gè)機(jī)器碼
  • 在可執(zhí)行文件開(kāi)始運(yùn)行以前,外部函數(shù)的機(jī)器碼由操作系統(tǒng)從磁盤(pán)上的該動(dòng)態(tài)庫(kù)中復(fù)制到內(nèi)存中,這個(gè)過(guò)程稱(chēng)為動(dòng)態(tài)鏈接(dynamic linking),也就是說(shuō),動(dòng)態(tài)鏈接是在需要調(diào)用接口時(shí)才會(huì)去將所用接口的二進(jìn)制代碼拷貝到內(nèi)存中。
  • 當(dāng)一個(gè)庫(kù)多文件使用時(shí),動(dòng)態(tài)庫(kù)只有一份,所以可以在多個(gè)程序間共享,所以動(dòng)態(tài)鏈接使得可執(zhí)行文件更小,節(jié)省了磁盤(pán)空間。–>操作系統(tǒng)采用虛擬內(nèi)存機(jī)制允許物理內(nèi)存中的一份動(dòng)態(tài)庫(kù)被要用到該庫(kù)的所有進(jìn)程共用,節(jié)省了內(nèi)存和磁盤(pán)空間。
  • 這里需要提一下的是,我們之前所提過(guò)的進(jìn)程地址空間中有一個(gè)共享區(qū),而一般動(dòng)態(tài)庫(kù)的代碼就映射在共享區(qū),所有進(jìn)程都共享著動(dòng)態(tài)庫(kù)的代碼。

動(dòng)靜態(tài)庫(kù)的對(duì)比

動(dòng)態(tài)庫(kù)被加載在內(nèi)存中,可以供多個(gè)使用庫(kù)的程序共享映射到自己的虛擬地址空間使用,因此可以減少頁(yè)面交換以及降低內(nèi)存中代碼冗余,并且因?yàn)榕c源程序模塊分離,因此開(kāi)發(fā)模式比較好。

而加載動(dòng)態(tài)庫(kù)的程序運(yùn)行速度相對(duì)較慢,因?yàn)閯?dòng)態(tài)庫(kù)運(yùn)行時(shí)加載,映射到虛擬地址空間后需要重新根據(jù)映射起始地址計(jì)算函數(shù)/變量地址。

靜態(tài)庫(kù)直接把二進(jìn)制代碼鏈接過(guò)來(lái),與動(dòng)態(tài)庫(kù)的使用恰好相反,其運(yùn)行速度相對(duì)較快,但消耗資源較多。

打包靜態(tài)庫(kù)

庫(kù)函數(shù)源文件:

//file1: Add.c
#include<stdio.h>

int Add(int a,int b)
{
    return a+b;
}

//file2: Sub.c
#include<stdio.h>
int Sub(int a,int b)
{
    return a-b;
}

生成靜態(tài)庫(kù)需要先生成目標(biāo)文件(.o)再進(jìn)行打包,故先編寫(xiě)相應(yīng)的源文件再將其編譯成目標(biāo)文件:

//生成兩個(gè)二進(jìn)制目標(biāo)文件
[root@VM-8-15-centos fighting]# gcc -c Sub.c -o Sub.o 
[root@VM-8-15-centos fighting]# gcc -c Add.c -o Add.o

此時(shí)的add.o和sub.o文件是已經(jīng)編譯好但還沒(méi)有鏈接的兩個(gè)文件;

此時(shí)再用 ar命令,歸檔工具將其打包成靜態(tài)庫(kù)

//將這倆二進(jìn)制目標(biāo)文件打包成靜態(tài)庫(kù)
[lyl@VM-4-3-centos 2022-3-14]$ ar -rc libmycal.a Add.o Sub.o //`rc`表示(replace and create)

查看靜態(tài)庫(kù)

 //查看靜態(tài)庫(kù)的目錄列表
[root@VM-8-15-centos fighting]#  ar -tv libmycal.a `tv`表示(列出靜態(tài)庫(kù)中文件 and verbose詳細(xì)信息)
rw-r--r-- 0/0    936 Jan 24 16:57 2023 Add.o
rw-r--r-- 0/0   1240 Jan 24 16:57 2023 Sub.o

使用靜態(tài)庫(kù)

將庫(kù)的頭文件和靜態(tài)庫(kù)都放到指定lib目錄下:

調(diào)用我們的庫(kù)接口代碼:

#include <stdio.h>
#include "add.h"
#include "sub.h"

int main()
{
  	int a = 10;
  	int b = 20;
  	printf("a+b:%d\n", Add(a, b));
  	printf("a-b:%d\n", Sub(a, b));
 	return 0;
}

編譯:

發(fā)現(xiàn)報(bào)錯(cuò): 這是因?yàn)間cc編譯時(shí)去鏈接庫(kù)和頭文件,是去默認(rèn)路徑以及當(dāng)前源文件路徑下尋找;

//gcc 尋找的默認(rèn)頭文件路徑:不建議污染原生庫(kù)
/usr/include
//gcc 尋找的默認(rèn)庫(kù)文件路徑:   
/lib , lib64 ......等

而我們將靜態(tài)庫(kù)打包到lib目錄下,gcc編譯時(shí)就找不到我們的庫(kù)了,因此我們編譯的時(shí)候,需要指定一些選項(xiàng),并且?guī)下窂?/lib;

因此,正確鏈接的指令為:

gcc -o test test.c -I ./lib -L ./lib  -lmycal -static
  • -I(大寫(xiě)i) + 指定路徑:告知gcc除了默認(rèn)路徑之外,還要去尋找這個(gè)指定路徑的頭文件
  • -L + 指定路徑:除默認(rèn)庫(kù)路徑以外,需要尋找這個(gè)指定路徑的庫(kù)
  • -l(小寫(xiě)L)+ 庫(kù)名稱(chēng):表示要具體鏈接的是哪一個(gè)庫(kù);(因?yàn)槁窂较聨?kù)可能不止一個(gè))
  • -static 選擇使用靜態(tài)庫(kù)的靜態(tài)鏈接

由此,我們就靜態(tài)鏈接生成了一個(gè)可執(zhí)行文件test,運(yùn)行test程序結(jié)果如下:

此時(shí)我們刪除靜態(tài)庫(kù),發(fā)現(xiàn)照樣可以運(yùn)行,因?yàn)殪o態(tài)庫(kù)中Add和Sub的二進(jìn)制代碼已經(jīng)被鏈接入test程序中了,不怕原生庫(kù)沒(méi)了!

可見(jiàn),有時(shí)候編譯選項(xiàng)多而雜,難記,特別是文件一多,寫(xiě)的很麻煩,介紹一個(gè)camke構(gòu)建項(xiàng)目的工具,C/C++開(kāi)發(fā)人員必會(huì)技能;文章鏈接

打包動(dòng)態(tài)庫(kù)

類(lèi)似與打包靜態(tài)庫(kù)使用的ar歸檔工具,動(dòng)態(tài)庫(kù)也有自己的語(yǔ)法,我們將生成動(dòng)態(tài)庫(kù)的依賴(lài)關(guān)系及方法寫(xiě)進(jìn)自動(dòng)化構(gòu)建工具(Makefile)中::

顯然手動(dòng)寫(xiě)Makefile和上面手動(dòng)打包靜態(tài)庫(kù)一樣,麻煩很多,我的評(píng)價(jià)是,直接cmake起飛;

注意:

  • 由于動(dòng)態(tài)庫(kù)在內(nèi)存中是可加載的,它可能在內(nèi)存中的任意位置,也可能被映射到進(jìn)程地址空間的每個(gè)區(qū)域,所以為了保證庫(kù)當(dāng)中的代碼執(zhí)行不會(huì)出錯(cuò),也就是要保證庫(kù)中的代碼是與位置無(wú)關(guān)的,因此生成.o文件時(shí)需要帶上-fPIC選項(xiàng)表示生成與位置無(wú)關(guān)碼。
  • 這里由于在依賴(lài)關(guān)系中已經(jīng)點(diǎn)明了要生成的目標(biāo)文件,故不帶上$@也可以
  • 打包動(dòng)態(tài)庫(kù)不是像靜態(tài)庫(kù)一樣先gcc -o再使用ar(歸檔工具);
  • 而是用gcc 帶上-shared選項(xiàng)表示生成共享動(dòng)態(tài)庫(kù)格式,這也體現(xiàn)了動(dòng)態(tài)庫(kù)代碼映射在共享區(qū)的特點(diǎn)

編寫(xiě)好Makefile之后 make指令構(gòu)建動(dòng)態(tài)庫(kù) libmycal.so:

使用動(dòng)態(tài)庫(kù)

和靜態(tài)庫(kù)一樣,我們把頭文件和.so庫(kù)文件放入lib目錄,gcc的時(shí)候帶上選項(xiàng);

 gcc -o test test.c -I ./lib -L ./lib -l mycal //因?yàn)槭莿?dòng)態(tài)鏈接 所以不用帶-static了

然后編譯過(guò)了,運(yùn)行程序時(shí)發(fā)現(xiàn)有問(wèn)題,打不開(kāi)動(dòng)態(tài)庫(kù)?:

既然編譯都聲稱(chēng)可執(zhí)行程序了,此時(shí)的可執(zhí)行程序是沒(méi)問(wèn)題的,因此已經(jīng)與編譯過(guò)程無(wú)關(guān)了;

那么這屬于運(yùn)行問(wèn)題,其實(shí)運(yùn)行時(shí)系統(tǒng)也會(huì)去默認(rèn)路徑下找到我們所使用的動(dòng)態(tài)庫(kù)但在默認(rèn)路徑下沒(méi)有我們的庫(kù)。

這里解決方法有多種,但我傾向于推薦下面這一種:

修改環(huán)境變量LD_LIBRARY_PATH,將動(dòng)態(tài)庫(kù)所在路徑.lib添加到該環(huán)境變量中,這樣程序在運(yùn)行時(shí)系統(tǒng)就能夠找到動(dòng)態(tài)庫(kù),從而運(yùn)行成功。

當(dāng)然,還可以拷貝我們的.so文件到系統(tǒng)共享庫(kù)路徑下, 一般指/usr/lib;

但是這可能會(huì)污染系統(tǒng)原生的庫(kù),一般不推薦這樣做。

還有一種方法,在我們的系統(tǒng)下有**/etc/ld.so.conf.d/**這個(gè)路徑:

我們可以在這個(gè)路徑下制造自己的.conf,然后再將自己的庫(kù)路徑寫(xiě)進(jìn)這個(gè)conf中;

但是還是有點(diǎn)污染了原生庫(kù),不建議;

小結(jié)

linux打包使用靜態(tài)庫(kù):

接口的.c源文件–>.o目標(biāo)二進(jìn)制文件–>ar rc(歸檔工具)進(jìn)行打包成.a靜態(tài)庫(kù),編譯程序使用時(shí)帶上-static;(注意帶上頭文件尋找路徑選項(xiàng))

linux打包使用動(dòng)態(tài)庫(kù):

接口的.c源文件–>.o目標(biāo)二進(jìn)制文件(需帶上-fPIC 與位置無(wú)關(guān))–>-shard 打包動(dòng)態(tài)庫(kù);(注意帶上頭文件尋找路徑選項(xiàng))+(注意添加動(dòng)態(tài)庫(kù)尋找路徑)

win下打包動(dòng)靜態(tài)庫(kù)

比如通過(guò)VS2019打包,由于是可視化界面方便操作,不再贅述,參考下方文章;

參考文章

總結(jié)

原文鏈接:https://blog.csdn.net/wtl666_6/article/details/128757570

欄目分類(lèi)
最近更新