靜態庫和動態庫

1、編譯過程

1.預處理:解釋并展開源程序當中的所有的預處理指令,此時生成 *.i 文件。
2.編譯:詞法和語法的分析,生成對應硬件平臺的匯編語言文件,此時生成 *.s 文件。
3.匯編:將匯編語言文件翻譯為對應處理器的二進制機器碼,此時生成 *.o 文件。
4.鏈接:將多個 *.o 文件合并成一個不帶后綴的可執行文件。

gec@ubuntu:~$ gcc hello.c -o hello.i -E
gec@ubuntu:~$ gcc hello.i -o hello.s -S
gec@ubuntu:~$ gcc hello.s -o hello.o -c
gec@ubuntu:~$ gcc hello.o -o hello -lcgcc hello.c  -o  hello

2.ELF格式

2.1概述

對于上述編譯過程,重點關注最后一步庫文件的鏈接(gcc hello.o -o hello -lc):鏈接實際上是將多個.o文件合并在一起的過程。這些 *.o 文件合并前是 ELF 格式,合并后也是 ELF 格式。
ELF全稱是 Executable and Linkable Format,即可執行可鏈接格式。ELF文件由多個不同的段(section)組成,如下圖所示:
在這里插入圖片描述
ELF格式的合并,實際上就是將多個文件中各自對應的段合并在一起,形成一個統一的ELF文件。在此過程中,必然需要對各個 *.o 文件中的靜態數據(包括常量)、函數入口的地址做統一分配和管理,這個過程就叫做 重定位,因此未經鏈接的單獨的 *.o 文件又被稱為可重定位文件,經過鏈接處理合并了相同的段的文件稱為可執行文件。
庫的本意是library圖書館,庫文件就是一個由很多 *.o 文件堆積起來的集合。

2.2 相關命令

(1) readelf 可以用來查看 ELF 格式文件的具體細節:

# 查看文件格式頭部信息
gec@ubuntu:~$ readelf -h a.out# 查看各個section信息
gec@ubuntu:~$ readelf -S a.out# 查看符號表
gec@ubuntu:~$ readelf -s a.out

3.庫文件

3.1概述

庫的本意是library圖書館,庫文件就是一個由很多 *.o 文件堆積起來的集合。本質上來說庫是一種可執行代碼的二進制形式,這個文件可以在編譯時由編譯器直接鏈接到可執行程序中,也可以在運行時由操作系統的runtime enviroment根據需要動態加載到內存中。

3.2分類

庫文件分為兩類:靜態庫和動態庫。如:

win32平臺下,靜態庫通常后綴為.lib,動態庫為.dll ;
linux平臺下,靜態庫通常后綴為.a,動態庫為.so 。靜態庫:libx.a
動態庫:liby.so

庫文件的名稱遵循這樣的規范:
lib庫名.后綴
其中,lib是任何庫文件都必須有的前綴,庫名就是庫文件真正的名稱,比如上述例子中兩個庫文件分別叫x和y,在鏈接它們的時候寫成 -lx 和 -ly ,后綴根據靜態庫和動態庫,可以是 .a 或者 .so:

  • 靜態庫的后綴:.a (archive,意即檔案)
  • 動態庫的后綴:.so (share object,意即共享對象)
    注意:不管是靜態庫,還是動態庫,都是可重定位文件 *.o 的集合。

3.3目的

  • 模塊化:庫文件將功能模塊化,使得程序結構更加清晰,易于管理和維護。
  • 簡化部署:使用庫文件可以簡化軟件的部署過程,因為它們可以在不同的程序之間共享,而不需要重復包含相同的代碼。
  • 動態鏈接:動態庫文件允許在程序運行時才鏈接,這樣可以在不重新編譯程序的情況下更新庫,提供了更大的靈活性。
  • 減少內存占用:使用動態庫時,由于多個程序可以共享同一份庫文件,因此可以減少每個程序的內存占用。
  • 易于更新和維護:庫文件的更新只需要替換原有文件,而不需要重新編譯使用該庫的所有程序,簡化了維護工作。
  • 跨平臺兼容性:庫文件可以被設計為跨平臺使用,增加了軟件的可移植性。
    總的來說,庫文件的使用是為了提高軟件開發的效率、靈活性和可維護性,同時減少資源的重復占用。

4、靜態庫

1所謂靜態庫,就是在靜態編譯時由編譯器到指定目錄尋找并且進行鏈接,一旦鏈接完成,最終的可執行程序中就包含了該庫文件中的所有有用信息,包括代碼段、數據段等。
靜態鏈接庫在程序編譯時會被鏈接到目標代碼中,目標程序運行時將不再需要改動態庫,移植方便,體積較大,浪費空間和資源,因為所有相關的對象文件與牽涉到庫都被鏈接合成一個可執行文件,這樣導致可執行文件的體積較大。

2.靜態庫的制作
假設功能文件 a.c、b.c 包含了一些通用的程序模塊,可以被其他程序復用,那么可以將它們制作成靜態庫,具體的步驟是:
第一步,制作 *.o 原材料
gec@ubuntu:~$ gcc a.c -o a.o -c
gec@ubuntu:~$ gcc b.c -o b.o -c

第二步,將 *.o 合并成一個靜態庫
gec@ubuntu:~$ ar crs libx.a a.o b.o

可見制作靜態庫非常簡單,制作完成之后,可以用命令 ar 查看庫中所包含的 *.o 文件:
gec@ubuntu:~$ ar -t libx.a

  1. 靜態庫的常見操作
    3.1 查看靜態庫中的 .o 列表
    gec@ubuntu:~$ ar t libx.a #(t意即table,以列表方式列出
    .o文件)
    a.o
    b.o

3.2 刪除靜態庫中的 .o 文件
gec@ubuntu:~$ ar d libx.a b.o #(d意即delte,刪除掉指定的
.o文件)
gec@ubuntu:~$ ar t libx.a
a.o

3.3 向靜態庫增加 .o 文件
gec@ubuntu:~$ ar r libx.a b.o #(r意即replace,添加或替換(重名時)指定的
.o文件)
gec@ubuntu:~$ ar t libx.a
a.o
b.o

3.4 提取靜態庫中的 .o 文件
gec@ubuntu:~$ ar x libx.a #(x意即extract,將庫中所有的
.o文件釋放出來)
gec@ubuntu:~$ ar x libx.a a.o #(指定釋放庫中的a.o文件)

4.靜態庫的使用
庫文件最大的價值,在于代碼復用。假設在上述庫文件所包含的 *.o 文件中,已經包含了若干函數接口,那么只要能鏈接這個庫,就無需再重復編寫這些接口,直接鏈接即可。

使用靜態庫 要是用靜態庫libadd.a,只需要包含add.h,就可以使用函數add()、sub()。

#include <stdio.h>
#include “add.h”
void main(){
printf(“add(5,4) is %d\n”,add(5,4));
printf(“sub(5,4) is %d\n”,sub(5,4));
}
靜態庫的文件可以放在任意的位置,編譯時只需要找到該庫文件即可。
gcc -c -I /home/xxxx/include -L /home/xxxxx/lib libadd.a test.c
1). 通過-I(是大i)指定對應的頭文件
2). 通過-L制定庫文件的路徑,libadd.a就是要用的靜態庫。
3). 在test.c中要包含靜態庫的頭文件。

總結:

編譯時:gcc a.c liba.a -o project
相當于liba.a 代替了b.c c.c參與編譯

5、動態庫

1.概述
不管是動態庫還是靜態庫,它們都是 *.o 文件的集合。動態庫指的是以.so后綴的庫文件。動態庫在程序編譯時并不會被鏈接到目標代碼中,而是在程序運行時才被載入,因為可執行文件體積較小。有了動態庫,程序的升級會相對比較簡單,比如某個動態庫升級了,只需要更換這個動態庫的文件,而不需要去更換可執行文件。但要注意的是,可執行程序在運行時需要能找到動態庫文件。可執行文件時動態庫的調用者。
在實際應用中,動態庫應用場合要遠多于靜態庫,因為雖然動態庫的運行時裝載特性會使得程序性能有略微的下降,但換來的是不僅僅節省了大量的存儲空間,更重要的是使得主程序和庫松耦合,不互相捆綁,當庫升級的時候,應用程序無需任何改動即可獲得新版庫文件的功能,這極大地提高了程序的靈活性。

2.庫文件命名
靜態庫的名字一般為libxxxx.a,其中xxxx是該lib的名稱;動態庫的名字一般為libxxxx.so.x.y.z,含義如下圖所示:
此處,符號鏈接的作用不是“快捷方式”,而是為了可以讓動態庫在升級版本的時候更加方便地向前兼容。一般而言,完整的動態庫文件名稱是:
lib庫名.so.主版本號.次版本號.修訂版本號
比如: libx.so.1.3.1
當動態庫迭代升級時,其版本號會發生相應的改變。比如下面的版本更迭:
2021年3月08日發布:libx.so.1.0.0
2021年4月02日發布:libx.so.1.0.1
2021年4月23日發布:libx.so.1.0.2
2021年5月18日發布:libx.so.1.0.3
2021年8月09日發布:libx.so.1.1.0
2021年9月12日發布:libx.so.1.1.1
可以看到,修訂版本號的更迭會比較頻繁,次版本號次之,主版本號再次之。為了避免每次版本號的修改而重新編譯,動態庫一般會用一個只帶主版本號的符號鏈接來鏈接程序,如:

gec@ubuntu:~$ ls -l
lrwxrwxrwx 1 root root    15 Jan 16 2020 libbsd.so.0 -> libbsd.so.0.8.7
-rw-r--r-- 1 root root 80104 Jan 16 2020 libbsd.so.0.8.7
gec@ubuntu:~$

這樣一來,未來不管版本號如何變遷,只要主版本號不變,那么用戶鏈接的庫名永遠都是 libbsd.so.0,而無需關心具體某個版本。而如果連主版本號都發生了改變,這一般是因為庫不再向前兼容,比如刪除了某些原有的接口,這種情況下,用戶就需要重新編譯程序。

3.制作庫文件常用的參數
首先需要了解gcc編譯庫要用到一些參數,很重要。
在這里插入圖片描述
4.制作動態庫
不管是靜態庫還是動態庫,都是用來被其他程序鏈接的一個個功能模塊。與靜態庫一致,制作動態庫的步驟如下:
將 *.c 編譯生成 *.o
將 *.o 編譯成動態庫

把add.c編譯成動態鏈接庫libadd.so
#第一步:將源碼編譯為 *.o
gcc -fPIC -o libadd.o -c add.c
#第二步:將 *.o 編譯為動態庫
gcc -shared -o libadd.so libadd.o

也可以直接使用一條命令
gcc -fPIC -shared -o libadd.so add.c

5.動態庫的使用
動態庫的編譯跟靜態庫并無二致,如:
gec@ubuntu:~$ pwd
/home/gec
gec@ubuntu:~$ ls lib/
libx.so
gec@ubuntu:~$ gcc main.c -o main -L./lib -lx
說明:
-L 選項后面跟著動態庫所在的路徑。
-l 選項后面跟著動態庫的名稱。
運行時鏈接
動態庫的最大特征,就是編譯鏈接后程序并不包含動態庫的代碼,這些程序會在每次運行時,動態地去尋找并定位其所依賴的庫文件中的模塊,這是他們為什么被稱為動態庫的原因。
也就是說,如果程序運行時找不到動態庫,運行就會失敗,例如:
gec@ubuntu:~$ ./main
報錯
出現上述錯誤的原因,就是因為運行程序 main 時,無法找到其所依賴的動態庫 libx.so,解決這個問題,有三種辦法:

1.編譯時預告:
gec@ubuntu:~$ gcc main.c -o main -L. -lx -Wl,-rpath=/home/gec/lib
2.設置環境變量:
gec@ubuntu:~$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/gec/lib
3.將庫文件拷貝到根目錄下的/lib里面

總結

可以通過動態庫(也稱為共享庫或共享對象文件)再構建一個動態庫。在鏈接過程中,一個動態庫可以依賴于其他動態庫或靜態庫。

當你使用編譯器(如gcc或clang)來構建動態庫時,你可以指定其他動態庫作為鏈接時的依賴。這些依賴的庫在運行時會被動態加載。

以下是一個簡單的例子,展示了如何使用gcc來從一個動態庫(libA.so)構建一個依賴于它的新動態庫(libB.so):

編譯和鏈接第一個動態庫(libA.so)
假設你有一個源文件a.c,你可以這樣編譯和鏈接它:

gcc -shared -o libA.so a.c

編譯和鏈接第二個動態庫(libB.so),它依賴于libA.so
假設你有一個源文件b.c,它調用了在libA.so中定義的函數。為了構建libB.so,你需要鏈接到libA.so:

gcc -shared -o libB.so b.c -L. -lA

注意-L.選項告訴鏈接器在當前目錄(.表示當前目錄)中查找庫,而-lA選項告訴鏈接器鏈接到名為libA.so的庫(注意,在-l選項后,庫名通常不包含前綴lib和后綴.so)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/36282.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/36282.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/36282.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

便攜式煙氣監測儀的應用主要有哪些?

煙氣分析儀是一種用于檢測和分析煙氣中各種成分和污染物含量的儀器&#xff0c;通過采集和處理煙氣樣品&#xff0c;對其中的各種成分進行定量分析。那么&#xff0c;便攜式煙氣監測儀的應用主要有哪些&#xff1f;為方便大家了解&#xff0c;下面就讓小編來為大家簡單介紹一下…

2-2到2-4

計算出所有人的平均年齡&#xff1a; val lines sc.textFile("/root/data/scala/people/page.txt") val count lines.count() val total lines.map(line > line.split(" ")(1)).map(t>t.trim.toInt).collect().reduce((a,b)>ab) val avgAge …

如何防止SQL注入

為了防止SQL注入攻擊&#xff0c;可以采取以下一系列的安全措施&#xff0c;這些措施結合了多篇參考文章中的關鍵信息和方法&#xff1a; 使用參數化查詢或預編譯語句&#xff1a; 這是防止SQL注入的最常見且最有效的方法之一。通過將用戶輸入的數據作為參數傳遞給SQL查詢語句…

[Python]根據文件路徑獲取文件所在目錄、文件名和后綴名

一、簡介 本文介紹了在python中如何根據文件的路徑名字&#xff08;字符串&#xff09;獲取文件所在目錄名、文件名&#xff08;帶后綴&#xff09;、文件名&#xff08;無后綴&#xff09;和文件后綴名。 二、代碼 假設文件路徑為/home/user/temp.txt&#xff0c;使用以下代…

壓縮pdf文件大小的方法,如何壓縮pdf格式的大小

pdf太大怎么壓縮&#xff1f;當你需要通過電子郵件發送一個PDF文件&#xff0c;卻發現文件太大無法成功發出時&#xff0c;這些情況下&#xff0c;我們都需要找到一種方法來壓縮PDF文件&#xff0c;以便更便捷地進行分享和傳輸。PDF文件的大小通常與其中包含的圖片、圖形和文本…

入門JavaWeb之 Response 下載文件

web 服務器接收到客戶端的 http 請求 針對這個請求&#xff0c;分別創建一個代表請求的 HttpServletRequest 對象&#xff0c;代表響應的 HttpServletResponse 對象 獲取客戶端請求過來的參數&#xff1a;HttpServletRequest 給客戶端響應一些信息&#xff1a;HttpServletRe…

數據庫索引失效的11種情況

MySQL中 提高性能 的一個最有效的方式是對數據表 設計合理的索引。索引提供了高效訪問數據的方法&#xff0c;并且加快查詢的速度&#xff0c;因此索引對查詢的速度有著至關重要的影響。使用索引可以 快速地定位 表中的某條記錄&#xff0c;從而提高數據庫査詢的速度&#xff0…

js獲取選中區域(window.getSelection的基本使用)

返回一個 Selection 對象&#xff0c;表示用戶選擇的文本范圍或光標的當前位置。 const selection window.getSelection() 1.toString() //光標選中的文本 const selectedText selection.toString() 2.getRangeAt() //返回一個包含當前選區內容的區域對象。 selection…

數據與文字的表示方法

目錄 1. 數據格式 1. 文本文件格式 2. 二進制文件格式 3. 數據庫格式 4. 壓縮格式 2. 數字機器碼表示 整數表示 浮點數表示 3. 字符與數組的表示方法 1. ASCII&#xff08;美國信息交換標準代碼&#xff09; 2. 擴展ASCII 3. Unicode 4. UTF-8&#xff08;8 位 Uni…

面試相關-接口測試常問的問題

1.為什么要做接口測試 (1)現在大多系統都是前后端分離的項目,前端和后端的進度可能不一樣,那為了盡早的進入測試,前端界面沒有開發完成的情況下,只要后端的接口開發完了,就可以提前做接口測試了; (2)基于安全考慮,只依賴前端進行限制,已經完全不滿足系統的安全性…

Power Pivot——常用DAX 函數

常用DAX 函數 以下這些函數是 DAX 中最常用的一部分&#xff0c;通過熟練掌握這些函數&#xff0c;你可以有效地進行數據分析和建模。 聚合函數 (Aggregation Functions) SUM() 用途&#xff1a;對指定列中的所有數值求和。 語法&#xff1a;SUM() 示例&#xff1a;SUM(Sale…

重生之我要學后端01--后端語言選擇和對應框架選擇

編程語言 后端開發通常需要掌握至少一種編程語言。以下幾種語言在后端開發中非常流行&#xff1a; Java&#xff1a;廣泛用于企業級應用程序。Python&#xff1a;因其易學性和強大的庫支持&#xff08;如Django和Flask&#xff09;而受歡迎。Node.js&#xff08;JavaScript&a…

電商賣家怎么快速采集復制1688全店寶貝到自己店鋪?淘/貓/拼/抖都適用!

1688上面的貨源品類豐富&#xff0c;很多賣家都是在這里找廠家&#xff0c;當我們找好廠家后&#xff0c;怎么將廠家店鋪里所有寶貝都復制到自己店鋪呢&#xff1f; 雖然1688平臺本身支持鋪貨到其他平臺&#xff0c;但一個個鋪貨太耗費時間了。 阿里巴巴中國站獲得1688商品詳…

【AI大模型RAG】深入探索檢索增強生成(RAG)技術

目錄 1. 引言2. RAG技術概述2.1 RAG技術的定義2.2 RAG技術的工作原理2.3 RAG技術的優勢2.4 RAG技術的應用場景 3. RAG的工作流程3.1 輸入處理3.2 索引建立3.3 信息檢索3.4 文檔生成3.5 融合與優化 4. RAG范式的演變4.1 初級 RAG 模型4.2 高級 RAG 模型4.3 模塊化 RAG 模型優化技…

會計報表分析

目錄 一. 會計報表的種類 \quad 一. 會計報表的種類 \quad 反應財務狀況的是資產負債表 反應經營成果的是利潤表 有時間點的就是靜態表 動態表就是有一個區間的, 比如一年, 一個季度等

探索這些有趣的API,讓你的應用與眾不同

在這個由數據驅動的時代&#xff0c;我們每天都在與各種應用程序和服務互動&#xff0c;卻很少意識到它們背后的技術奇跡。API&#xff0c;作為這些互動的幕后英雄&#xff0c;不僅簡化了開發過程&#xff0c;還擴展了技術的邊界。有趣的API&#xff0c;特別是那些能夠激發創新…

QT 如何儲存多種數據類型(QVariant )

QVariant 是 Qt 框架中用于存儲各種數據類型的類。它提供了一個強大的類型系統&#xff0c;允許你在運行時存儲和檢索多種類型的數據&#xff0c;而不需要在編譯時確定類型。QVariant 的主要優點在于它的靈活性和通用性&#xff0c;這使得它在 Qt 的很多組件和機制中都被廣泛使…

時間戳是什么,如何使用時間戳

時間戳&#xff08;Timestamp&#xff09;是表示特定時間點的數值&#xff0c;通常以自1970年1月1日00:00:00 UTC&#xff08;協調世界時&#xff09;以來的秒數或毫秒數來表示。這個時間點被稱為Unix紀元&#xff08;Unix epoch&#xff09;。時間戳廣泛用于計算機系統中&…

數據結構教材關于C/C++的研究

變量 指針 引用 變量 普通變量表示一個內存空間&#xff0c;直接printf是內存空間里的值 結構體 定義一個結構體類型變量為什么必須用指針&#xff1f; 因此無法確定結構體需要多少空間&#xff0c;改用指針可以解決這個問題&#xff0c;因為指針的大小是固定的 指針 指…

HTTP協議和Nginx

一、HTTP協議和Nginx 1.套接字Socket 套接字Socket是進程間通信IPC的一種實現&#xff0c;允許位于不同主機&#xff08;或同一主機&#xff09;上不同進程之間進行通信和數據交換&#xff0c;SocketAPI出現于1983年BSD4.2實現在建立通信連接的每一端&#xff0c;進程間的傳輸…