多文件和靜態/動態鏈接以及虛擬內存管理

多目標文件鏈接

//stack.c
char stack[512];
int top =-1;
void push(char c){stack[++top] = c;
}char pop(void){return stack[top--];
}int is_empty(void){return top == 1;
}// main.c
#include <stdio.h>
int a,b = 1;
int main(){
push('a');
push('b');
push('c');
while(!is_empty())putchar(pop());putchar('\n');return 0;
}

通過 readelf -a main命令可以看到

  • main的.bss段合并了 main.o和stack.o的.bss段,包含了變量a和stack;
  • main的.data段合并了main.o和 stack.o的.data段,其中包含了變量b和top;
  • main的.text段合并了main.o和 stack.o的.text段
image-20240525234842552

PS: GDB如何調試多個文件的code

//main.c 和 stack.c 
(gdb)list stack.c:1 
(gdb)b stack.c:10     

定義和聲明

extern和static

  • 用extern聲明的函數名具有external linkage
  • 用static聲明的函數名具有internal linkage
  • 函數默認是extern的

凡是被多次聲明的變量或函數,必須有且只有一個聲明是定義,如果有多個定義,或者一個定義都沒有,鏈接器就無法完成鏈接。

變量聲明和函數聲明有一點不同,函數聲明的 extern關鍵字可以省略,而變量聲明如果不寫 extern意思就完全變了

用 static關鍵字聲明具有 Internal Linkage,保護了函數的內部狀態,是一種封裝

頭文件

通過宏定義避免硬編碼

//stack.h 
#ifdef STACK_H 
#define STACK_H void push(char); char pop(void); int is_empty(void); 
#endif//main.c 
#include "stack.h"          

對于用角括號包含的頭文件**,gcc首先查找**-I**選項指定的目錄,然后查找系統的頭文件目錄);

而對于用引號包含的頭文件**,gcc首先查找包含這個頭文件的當前文件所在的目錄,然后查找**-I選項指定的目錄,然后查找系統的頭文件目錄

則可以用gcc- c maln.c編譯,gcc會自動在main.C所在的目錄中找到stack. h。假如把 stack.h移到一個子目錄下

image-20240525235130253

則需要用gcc- c main.c -Istack編譯,用-I選項告訴gcc頭文件要到子目錄 stack里找

在#include預處理指示中可以使用相對路徑,例如把上面的代碼改成#include “stack/stack,h”,那么編譯時就不需要加-Istack選項了,因為是main.c要包含頭文件,gcc會自動在main.c所在的目錄中查找,而頭文件相對于main.c所在目錄的相對路徑正是 stack/ stack.h

PS:gcc -E可以產生預編譯文件

避免頭文件被重復包含的方法為header guard

寫.C文件和頭文件時一般來說應遵循以下原則:

  • C文件中可以有變量或函數定義,而.h文件中應該只有變量或函數聲明而沒有定義。
  • 不要把一個C文件包含到另一個C文件中。

靜態庫

把一組代碼編譯成一個庫,很多項目中復用

例如將stack.c文件拆分為四個文件,main.c保持不變

image-20240525235204471
gcc -c stakc/stack.c stack/push.c stack/pop.c stack/is_empty.c
ar rs libstack.a stack.o push.o pop.o is_empty.o
# r表示將文件打包進libstack.a中,s表示為靜態鏈接庫
# 等價于
ar r libstack.a stack.o push.o pop.o is_empty.o
ranlib libstack.a
# 鏈接libstack.a main.c
gcc mian.c -L. -lstack -Istack -o main

-L選項告訴編譯器去哪里找需要的庫文件L.表示在當前目錄找。-lstack選項告訴編譯器要鏈接 libstack庫-I選項告訴編譯器去哪里找頭文件

編譯器默認會找哪些目錄,用-print-search-dirs選項查看一下

gcc -print-search-dirs            

在處理-lstack選項時,gcc首先到-L選項指定的目錄下查找,看有沒有共享庫Iibstack.so,如果有就鏈接它,否則再找有沒有靜態庫 Iibstack,a,如果有就鏈接它,如果還是沒有,就到默認搜索路徑下按同樣的步驟查找。

gcc在鏈接時優先考慮共享庫,其次才是靜態庫,如果希望gcc只考慮靜態庫,可以指定-static選項。

main.c只調用了push這一個函數,所以鏈接生成的可執行文件中也只有push而沒有pop和 is_empty。鏈接器從靜態庫中只取出需要的目標文件來做鏈接,不需要的目標文件可以不鏈接

共享庫

組成共享庫的目標文件和一般的目標文件有所不同,在編譯時要加-fPIC選項,即位置無關編碼

gcc -c -fPIC stakc/stack.c stack/push.c stack/pop.c stack/is_empty.c

指令中凡是用到stack和top變量的地址都用0x0表示,以備在重定位時修改。

image-20240526001510554

原來指令中的0x0被改成了0x804a010和0x804a040,這樣做了重定位之后,各段的加載地址就定死了,因為在指令中使用了絕對地址

image-20240526001534124

和先前的結果不同,指令中的0x0(%ebx)被修改成-0xc(ebx)和-0x8(%ebx),而不是修改成絕對地址。所以共享庫各段的加載地址并沒有定死,可以加載到任意位置。因為指令中的地址都是相對于ebx的,沒有使用絕對地址,只要根據實際的加載情況修改ebx就可以了,這就是位置無關代碼的特點.

image-20240526001605212

對比前后的指令差異

image-20240525235450330

-0xc(%ebx)這個地址并不是變量top的地址,這個地址的內存單元中又保存了另外一個地址,而它才是變量top的地址。指令mov -0xc(%ebx),%eax是從地址ebx-12取出變量top的地址傳給eax,而指令mov (%eax),%eax才是從top的地址取出top的值傳給eax。指令lea 0x1(%eax),%edx是把top的值加1存到edx中。lea指令算出第一個操作數所代表的地址,但并不訪問內存,而是直接把這個地址傳給第二個操作數。我們知道x86的內存尋址方式涉及加法和乘法運算,lea指令只是利用尋址電路做加法和乘法運算,而不是真的尋址,

image-20240526001319076

將main.c文件和共享庫鏈接

image-20240525235509342

用Ldd命令査看可執行文件依賴于哪些共享庫:

image-20240525235515592

動態鏈接器在那些目錄搜索共享庫?

  1. 首先在環境變量LD_ LIBRARY_PATH保存的路徑中查找
  2. 然后從緩存文件/etc/ld.so. cache中查找這個緩存文件是由 ldconfig命令讀取配置文件/etc/ld.so.conf生成的
  3. 如果上述步驟都找不到,則到默認的系統庫文件目錄中查找,先是/usr/ib然后是/Lib。

最常用的方法。把lsibtack.so所在目錄的絕對路徑(比如/home/ akaedu/somedir)添加到配置文件/etc/ld.so.conf(該文件中每個路徑占一行),然后運行ldconfig命令:

image-20240525235606585

再查看動態庫

image-20240525235617265

函數的動態鏈接

和鏈接靜態庫的情況不同,push函數的指令沒有鏈接到可執行文件中,而且call 86483d8-push@pLt>這條指令調用的也不是push函數的地址,而是plt段里的地址。PLT是 Procedure Linkage Table的縮寫,.plt段里保存的也是指令,和.text一起合并到 Text Segment

image-20240525235657934

共享庫命名

按照共享庫的命名慣例,每個共享庫有三個文件名:real name、 soname和 linker name

真正的庫文件(而不是符號鏈接)的名字是 real name,包含完整的共享庫版本號,例如上面的 libcap.so.1.10、libc-2.8.90.so等

soname是符號鏈接的名字,只包含共享庫的主版本號

但對于依賴libcap.So.1的程序來說,真正的庫文件不管是 Libcap.S0.1.16還是Libcap.so.1.11都可以用,所以使用共享庫可以很方便地升級庫文件而不需要重新編譯程序,這是靜態庫所沒有的優點。注意libc的版本編號有一點特殊,libc-2.8.90.s0的主版本號是6而不是2或28

linker name僅在編譯鏈接時使用,gcc的-L選項應該指定 linker name所在的目錄。有的 linker name是庫文件的一個符號鏈接,有的 linker name是一段鏈接腳本。

虛擬內存管理

ps //查看進程
cat /porc/29977/maps //查看進程地址空間
image-20240525235734531

堆空間的地址上限(0x09497000)稱為 Break,堆空間要向高地址增長就要抬高 Break,映射新的虛擬內存頁面到物理內存,這是通過系統調用brk實現的, malloc函數也是調用brk向內核請求分配內存的。

操作系統虛擬內存控制機制的作用

(1)可以控制物理內存的訪問權限

物理內存本身是不限制訪問的,任何地址都可以讀寫,而操作系統要求不同的頁面具有不同的訪問權限,這是利用CPU模式和MMU的內存保護機制實現的。錯誤的指令或惡意代碼的破壞能力受到了限制,最多使當前進程因段錯誤而終止,不會影響到整個系統的穩定性。

(2)使每個進程有獨立的地址空間

不同進程中相同的VA被MMU映射到不同的PA,因此在某一個進程中訪問任何虛擬地址都不可能訪問到屬于另外一個進程的物理內存頁面,并且每個進程都認為自己獨占0x0000000 xbffffffff 整個用戶地址空間。獨立地址空間的好處是:任何一個進程由于執行了錯誤指令或惡意代碼而導致的非法內存訪問都不會意外改寫其他進程的數據,也不會影響其他進程的運行;鏈接器和加載器的實現也比較容易,不必考慮各進程的地址范圍是否沖突

image-20240525235841597

兩個進程都是bash進程, Text Segment是一樣的,并且 Text Segment是只讀的,不會被改寫,因此操作系統安排兩個進程的TextSegment共享相同的物理頁面。由于每個進程都有自己的一套VA到PA的映射表,在一個進程中通過VA只能訪問到屬于自己的物理頁面,而不會訪問到其他進程的物理頁面。

(3)VA到PA的映射會給分配和釋放內存帶來方便

物理地址不連續的幾塊內存可以映射成虛擬地址連續的一塊內存。比如要用 malloc分配一塊很大的內存空間,雖然有足夠多的空閑物理內存,卻沒有足夠大的連續空閑內存,這時就可以分配多個不連續的物理頁面而映射到連續的虛擬地址范圍。

image-20240525235849454

(4)一個系統如果同時運行著很多進程,為各進程分配的內存之和可能會大于實際可用的物理內存,虛擬內存管理機制使這種情況下各進程仍然能夠正常運行

進程訪問的是虛擬內存頁面,這些頁面的數據可以保存在物理頁面中,也可以臨時保存在磁盤上而不占用物理頁面,可以在磁盤上開一個分區或者建一個文件專門用于臨時保存虛擬內存頁面的數據,這稱為交換設備( Swap Device)。啟用了交換設備之后,系統中可分配的內存總量等于物理內存的大小與交換設備的大小之和

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

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

相關文章

Vue項目中npm run build 卡住不執行的幾種情況(實戰版)

方法一 一&#xff1a;比較常見是鏡像導致的原因 我們可以找到build/check-versions文件 將這段代碼注釋,重新運行就可以解決這個問題 if (shell.which(npm)) {versionRequirements.push({name: npm,currentVersion: exec(npm --version),versionRequirement: packageConfig.en…

MySQL 存儲過程返回更新前記錄

在MySQL中&#xff0c;如果我們想在存儲過程中返回更新前的記錄&#xff0c;這通常不是直接支持的&#xff0c;因為UPDATE語句本身不返回更新前的數據。但是&#xff0c;我們可以通過一些策略來實現這個需求。 1.MySQL 存儲過程返回更新前記錄常用的方法策略 以下是一個常見的…

應用程序圖標提取

文章目錄 [toc]提取過程提取案例——提取7-zip應用程序的圖標 提取過程 找到需要提取圖標的應用程序的.exe文件 復制.exe文件到桌面&#xff0c;并將復制的.exe文件后綴改為.zip 使用解壓工具7-zip解壓.zip文件 在解壓后的文件夾中&#xff0c;在.rsrc/ICON路徑下的.ico文件…

代碼隨想錄-Day20

654. 最大二叉樹 給定一個不重復的整數數組 nums 。 最大二叉樹 可以用下面的算法從 nums 遞歸地構建: 創建一個根節點&#xff0c;其值為 nums 中的最大值。 遞歸地在最大值 左邊 的 子數組前綴上 構建左子樹。 遞歸地在最大值 右邊 的 子數組后綴上 構建右子樹。 返回 nums…

ROS | 激光雷達包格式

ros激光雷達包格式&#xff1a; C實現獲取雷達數據 &#xff1a; C語言獲取雷達數據&#xff1a; Python語言獲取雷達數據&#xff1a; python不需要編譯&#xff0c;但是需要給它一些權限 chmod x lidar_node.py(當前的文件名字&#xff09; C實現雷達避障&#xff1a; python…

【Xilinx】常用的全局時鐘資源相關Xilinx器件原語

1 概述 常用的與全局時鐘資源相關的Xilinx器件原語包括&#xff1a; IBUFGIBUFGDS、OBUFGDS 和 IBUFDS、OBUFDSBUFGBUFGPBUFGCEBUFGMUXBUFGDLLIBUFDS_GTXE1IBUFDS_GTE2IBUFDS_GTE3OBUFDS_GTE3IBUFDS_GTE4OBUFDS_GTE4DCM 剛開始看到這寫源語&#xff0c;免不了好奇這些源語對應的…

IDEA如何對多線程進行debug

開發中使用到多線程的時候不少,但是debug起來還是比較困難的,因為默認每次只會進入一個線程,這樣有些問題是發現不了的,其實IDEA也是支持進入每個線程來debug的 寫一個簡單的demo public class ThreadDebug {public static void main(String[] args) {MyThread myThread new…

c++ set/multiset容器

在C標準庫中&#xff0c;set 和 multiset 是兩種非常有用的關聯容器&#xff0c;它們包含唯一元素&#xff08;對于set&#xff09;或可重復元素&#xff08;對于multiset&#xff09;&#xff0c;并且默認情況下這些元素都是自動排序的。它們通過鍵&#xff08;即存儲的元素本…

異方差的Stata操作(計量114)

以數據集 nerlove.dta 為例&#xff0c;演示如何在 Stata 中處理異方差。 此數據集包括以下變量&#xff1a; tc ( 總成本 ) &#xff1b; q ( 總產量 ) &#xff1b; pl ( 工資率 ) &#xff1b; pk ( 資本的使用成本 ) &#xff1b; pf ( 燃料價格 ) &#xff1b; …

GESP等級大綱

CCF編程能力等級認證概述 CCF編程能力等級認證&#xff08;GESP&#xff09;為青少年計算機和編程學習者提供學業能力驗證的規則和平臺。GESP覆蓋中小學階段&#xff0c;符合年齡條件的青少年均可參加認證。C & Python編程測試劃分為一至八級&#xff0c;通過設定不同等級…

[自動駕駛技術]-6 Tesla自動駕駛方案之硬件(AI Day 2021)

1 硬件集成 特斯拉自動駕駛數據標注過程中&#xff0c;跨250萬個clips超過100億的標注數據&#xff0c;無論是自動標注還是模型訓練都要求具備強大的計算能力的硬件。下圖是特斯拉FSD計算平臺硬件電路圖。 1&#xff09;神經網絡編譯器 特斯拉AI編譯器主要針對PyTorch框架&am…

AI數據面臨枯竭

Alexandr Wang&#xff1a;前沿研究領域需要大量當前不存在的數據&#xff0c;未來會受到這個限制 Alexandr Wang 強調了 AI 領域面臨的數據問題。 他指出&#xff0c;前沿研究領域&#xff08;如多模態、多語言、專家鏈式思維和企業工作流&#xff09;需要大量當前不存在的數…

壓縮能力登頂 小丸工具箱 V1.0 綠色便攜版

平常錄制視頻或下載保存的視頻時長往往都很長&#xff0c;很多時候都想要裁剪、 截取出一些“精華片段”保留下來&#xff0c;而不必保存一整個大型視頻那么浪費硬盤空間… 但如今手機或電腦上大多數的視頻剪輯軟件&#xff0c;切割視頻一般都要等待很長時間導出或轉換&#…

【C語言回顧】編譯和鏈接

前言1. 編譯2. 鏈接結語 上期回顧: 【C語言回顧】文件操作 個人主頁&#xff1a;C_GUIQU 歸屬專欄&#xff1a;【C語言學習】 前言 各位小伙伴大家好&#xff01;上期小編給大家講解了C語言中的文件操作&#xff0c;接下來我們講解一下編譯和鏈接&#xff01; 1. 編譯 預處理…

H5掃描二維碼相關實現

H5 Web網頁實現掃一掃識別解析二維碼&#xff0c;就現在方法的npm包就能實現&#xff0c;在這個過程中使用過html5-qrcode 和 vue3-qr-reader。 1、html5-qrcode的使用 感覺html5-qrcode有點小坑&#xff0c;在使用的時候識別不成功還總是進入到錯誤回調中出現類似NotFoundExc…

Python怎樣將PDF拆分成多個文件

在 Python 中&#xff0c;你可以使用 PyPDF2 庫來拆分 PDF 文件。以下是一個簡單的示例&#xff0c;演示如何將一個 PDF 文件拆分為多個單頁 PDF 文件。 首先&#xff0c;你需要安裝 PyPDF2 庫。如果尚未安裝&#xff0c;可以使用以下命令進行安裝&#xff1a; pip install P…

天干物燥小心火燭-智慧消防可視化大屏,隱患防治于未然。

智慧消防可視化大屏通常包括以下內容&#xff1a; 1.實時監控&#xff1a; 顯示消防設備、傳感器、監控攝像頭等設備的實時狀態和數據&#xff0c;包括火災報警、煙霧報警、溫度報警等。 2.建筑結構&#xff1a; 顯示建筑物的結構圖和平面圖&#xff0c;包括樓層分布、消防通…

VLC播放器(全稱VideoLAN Client)

一、簡介 VLC播放器&#xff08;全稱VideoLAN Client&#xff09;是一款開源的多媒體播放器&#xff0c;由VideoLAN項目團隊開發。它支持多種音視頻格式&#xff0c;并能夠在多種操作系統上運行&#xff0c;如Windows、Mac OS X、Linux、Android和iOS等。VLC播放器具備播放文件…

特殊變量筆記3

輸入一個錯誤命令, 在輸出$? 特殊變量&#xff1a;$$ 語法 $$含義 用于獲取當前Shell環境的進程ID號 演示 查看當前Shell環境進程編號 ps -aux|grep bash輸出 $$ 顯示當前shell環境進程編號 小結 常用的特殊符號變量如下 特殊變量含義$n獲取輸入參數的$0, 獲取當前She…