程序員的自我修養(yǎng)總結(jié)_第1頁
程序員的自我修養(yǎng)總結(jié)_第2頁
程序員的自我修養(yǎng)總結(jié)_第3頁
免費(fèi)預(yù)覽已結(jié)束,剩余48頁可下載查看

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、目錄第一章溫故而知新 6第二節(jié)萬變不離其宗6第3節(jié) 站得高看得遠(yuǎn) 7第4節(jié)操作系統(tǒng)的功能7不要讓CPU打盹7設(shè)備驅(qū)動(dòng)81.5 內(nèi)存不夠怎么辦? 8關(guān)于隔離81.5.2 分段91.5.3 分頁91.6眾人拾柴火焰高 10線程基礎(chǔ)10線程安全 11多線程內(nèi)部情況14第二章編譯和鏈接 152.1被隱藏了的過程 15預(yù)編譯152.1.2 編譯152.1.3 匯編152.1.4 鏈接162.2編譯器做了什么16詞法分析16語法分析16語義分析16中間語言生成 17目標(biāo)代碼的生成與優(yōu)化 172.3鏈接器年齡比編譯器長 182.4模塊拼接一一靜態(tài)鏈接 18第三章目標(biāo)文件里有什么 183.1目標(biāo)文件的格式19

2、3.2目標(biāo)文件是什么樣的 193.3 挖掘 SimpleSection.o 203.3.3 BSS段 20其他段203.4 ELF文件結(jié)構(gòu)描述 20文件頭213.4.2 段表21重定位表 22字符串表 223.5鏈接的接口 一一符號(hào) 223.5.1 ELF符號(hào)表結(jié)構(gòu) 23特殊符號(hào) 23符號(hào)修飾與函數(shù)簽名 24弱符號(hào)和強(qiáng)符號(hào) 243.6調(diào)試信息25第4章靜態(tài)鏈接254.1空間與地址分配25相似段合并25符號(hào)地址的確定 264.2符號(hào)解析與重定位 26重定位表26符號(hào)解析 27指令修正方式 274.3 COMMON 塊27重復(fù)代碼消除 284.4.2 全局構(gòu)造與析構(gòu) 294.4.3 C+與 ABI

3、 294.5靜態(tài)庫鏈接 304.6鏈接過程控制 30鏈接過程腳本30最“小”的程序 31使用Id鏈接腳本314.6.4 Id鏈接腳本語法簡(jiǎn)介314.7 BFD 庫31第 5 章 WINDOWS PE/COFF315.1 Windows的二進(jìn)制文件格式 PE/COFF315.2 PE 的前身 COFF325.3鏈接指示信息 325.4調(diào)試信息325.5大家都有符號(hào)表325.6 WINDOWS 下的 ELFPE32第6章可執(zhí)行文件的裝載與進(jìn)程 336.1進(jìn)程的虛擬地址空間 336.2裝載的方式 33覆蓋裝入33頁映射346.3從操作系統(tǒng)的角度看可執(zhí)行文件的裝載 34進(jìn)程的建立 346.4 進(jìn)程虛存

4、空間的分布 356.4.1 ELF文件鏈接視圖和執(zhí)行視圖 35堆和棧36堆的最大申請(qǐng)數(shù)量 36段地址對(duì)齊36進(jìn)程棧初始化376.5 Linux內(nèi)核裝載 ELF過程簡(jiǎn)介 376.6 Windows PE 的裝載 38第7章動(dòng)態(tài)鏈接387.1為什么要?jiǎng)討B(tài)鏈接387.2簡(jiǎn)單的動(dòng)態(tài)鏈接例子 397.3地址無關(guān)代碼40固定裝載地址的困擾 40裝載時(shí)重定位 40地址無關(guān)代碼 40共享模塊的全局變量問題 42代碼段地址無關(guān)性 437.4延遲綁定(PLT). 437.5動(dòng)態(tài)鏈接相關(guān)結(jié)構(gòu) 447.5.1 “ .in”i段457.5.2 “ dyna”段45動(dòng)態(tài)符號(hào)表 45動(dòng)態(tài)鏈接重定位表 45動(dòng)態(tài)鏈接時(shí)進(jìn)程堆棧

5、初始化信息 467.6動(dòng)態(tài)鏈接的步驟和實(shí)現(xiàn) 46動(dòng)態(tài)鏈接器自舉46裝載共享對(duì)象 47重定位和初始化 477.6.4 Linux動(dòng)態(tài)鏈接器的實(shí)現(xiàn) 477.7顯示運(yùn)行時(shí)鏈接 48打開動(dòng)態(tài)庫 487.7.2 dlsym() 487.7.3 dlerror() 487.7.4 dlclose() 49第8章Linux共享庫的組織 498.1共享庫版本49共享庫兼容性 49共享庫版本命名 498.1.3 SO-NAME程序需要記錄什么 508.2符號(hào)版本50基于符號(hào)的版本機(jī)制 508.2.3 Linux中的符號(hào)版本 518.3共享庫系統(tǒng)路徑518.4共享庫的查找過程 518.5環(huán)境變量528.6共享庫的

6、創(chuàng)建與安裝 52共享庫的創(chuàng)建52共享庫的安裝53共享庫構(gòu)造和析構(gòu)函數(shù) 53共享庫腳本53第 9 章 Windows 下的動(dòng)態(tài)鏈接 549.1 dll 介紹54基地址和 RVA549.1.3 dll共享數(shù)據(jù)段549.1.4 dll的簡(jiǎn)單例子54使用模塊定義文件 559.1.8 DLL顯示運(yùn)行時(shí)鏈接 559.2符號(hào)導(dǎo)出導(dǎo)入表55導(dǎo)出表559.2.2 EXP文件56導(dǎo)入表56導(dǎo)入函數(shù)的調(diào)用 569.3 DLL優(yōu)化57重定基地址 579.3.2 序號(hào)58導(dǎo)入函數(shù)綁定 589.4 C+與動(dòng)態(tài)鏈接589.5 DLL HELL59第4部分庫與運(yùn)行庫60第10章內(nèi)存6010.1程序的內(nèi)存布局 6010.2棧與

7、調(diào)用慣例61什么是棧61調(diào)用慣例61函數(shù)返回值傳遞6310.3堆與內(nèi)存管理63什么是堆6310.3.2 Linux進(jìn)程堆管理 6310.3.3 Windows 進(jìn)程堆管理64堆分配算法64第11章運(yùn)行庫6511.1入口函數(shù)和程序初始化 65程序從 main開始執(zhí)行嗎 65入口函數(shù)是如何實(shí)現(xiàn)的 65運(yùn)行庫與I/O 6611.1.4 MSVC CRT勺入口函數(shù)初始化 6611.2 C/C+運(yùn)行庫 6711.2.1 C語言運(yùn)行庫 6711.2.2 C語言標(biāo)準(zhǔn)庫 6711.2.3 glibc 和 MSVC CRT6711.3 運(yùn)行庫與多線程 6811.3.1 CRT的多線程困擾 6811.3.2 CR

8、T 改進(jìn) 68線程局部存儲(chǔ)實(shí)現(xiàn) 6911.4 C+全局構(gòu)造和析構(gòu) 6911.4.1 glibc全局構(gòu)造和析構(gòu) 691142 MSVC的全局構(gòu)造和析構(gòu) 7011.5 fread 的實(shí)現(xiàn)7111.5.1 緩沖7111.5.2 fread_s 7111.5.3 _fread_ no lock_s 7111.5.4 _read 71文本換行 7211.5.6 fread 回顧 72第12章系統(tǒng)調(diào)用與API 7212.1系統(tǒng)調(diào)用介紹72什么是系統(tǒng)調(diào)用 72系統(tǒng)調(diào)用的弊端 7212.2系統(tǒng)調(diào)用原理 73基于INT的Linux的經(jīng)典系統(tǒng)調(diào)用實(shí)現(xiàn) 7312.2.3 Linux的新型系統(tǒng)調(diào)用機(jī)制 7312.3

9、Windows API 7412.3.1 Windows API 概覽7412.3.2 為什么要使用 Windows API ? 74第13章運(yùn)行庫的實(shí)現(xiàn)7413.1 C語言運(yùn)行庫 74A.1字節(jié)序74第一章溫故而知新第二節(jié)萬變不離其宗凡是單純講史的章節(jié)我全部略去。本節(jié)講的主要是由CPU、內(nèi)存和I/O之間速度不匹配而設(shè)計(jì)的硬件架構(gòu)及其 發(fā)展。這個(gè)就不用細(xì)說了 CPU最快,內(nèi)存次之,I/O更慢。由于CPU和內(nèi)存速度 還算接近,所以把CPU和內(nèi)存算作一類,I/O單獨(dú)算作一類。當(dāng)然這里說的I/O 是指I/O設(shè)備,并不是操作。隨著發(fā)展CPU頻率越來越高,處理速度越來越快,內(nèi)存跟不上節(jié)奏了,它 們之間的

10、I/O也出現(xiàn)了速度不匹配的問題。因?yàn)镮/O設(shè)備可分為高速設(shè)備和低速設(shè)備兩種,所以為高速搭配北橋,低速 搭配南橋。它們之間的關(guān)系可用下圖表示:CPU的頻率只能達(dá)到4GHz無法提升,這是由CPU制造工藝決定的,是個(gè) 瓶頸,目前還無法突破。一個(gè)CPU能力有限,那就讓多個(gè)CPU共同工作提升效率。但是這樣的CPU 陣列各部件利用率不高,于是,發(fā)展出了多核心,其他部件共享的多核 CPU設(shè) 計(jì)。說白了,原來的CPU里面每個(gè)CPU一個(gè)核心,除此之外還有圍繞這個(gè)核的其他部件。但是現(xiàn)在多核CPU除了核心彼此獨(dú)立外,其他的部件是共享的 這一節(jié)就這么點(diǎn)內(nèi)容。第3節(jié)站得咼看得遠(yuǎn)Applktlons:Web Browse

11、rVMeo PlayerWord ProcftssorEmail ClientImage Viewer從下圖可以看出計(jì)算機(jī)的結(jié)構(gòu)大概是這樣的:Development Tools:C/C+ CompilerAssemblerLibrary ToolsDebug TootsDevelopment Libraries-Operating Systeni APIRuntime LibraryCalOperating System KernelHardware最底層是硬件,它提供硬件規(guī)格描述。再往上是操作系統(tǒng)內(nèi)核,它提供系統(tǒng) 調(diào)用。再往上是運(yùn)行庫,它提供各種系統(tǒng) API。再往上就是各種系統(tǒng)軟件了。這種設(shè)

12、計(jì)具有上層屏蔽下層,上層提供接口的特點(diǎn)。這一節(jié)對(duì)接口的解釋非常好。作者說接口是一種協(xié)議,協(xié)議二字比較貼切。當(dāng)然這個(gè)協(xié)議不是計(jì)算機(jī)網(wǎng)絡(luò)中的protocol。第4節(jié)操作系統(tǒng)的功能有二。1、提供抽象接口。2、管理硬件不要讓CPU打盹操作系統(tǒng)經(jīng)歷了從多道程序設(shè)計(jì)、分時(shí)操作系統(tǒng)、到多任務(wù)操作系統(tǒng)等階段 多道程序設(shè)計(jì)是指CPU空閑的時(shí)候出讓CPU以提高CPU利用率的設(shè)計(jì); 分時(shí)是指給每個(gè)程序固定的時(shí)間片執(zhí)行, 時(shí)間片一到就停止的設(shè)計(jì),不過這個(gè)時(shí) 間片是輪轉(zhuǎn)著用的,不是一個(gè)程序用完了就沒了;多任務(wù)就是現(xiàn)在操作系統(tǒng)設(shè)計(jì) 了,程序以進(jìn)程的方式存在。搶占:OS對(duì)程序執(zhí)行具有絕對(duì)的控制權(quán),OS依據(jù)一定標(biāo)準(zhǔn)判斷該剝奪

13、哪個(gè) 程序的執(zhí)行就剝奪,想讓哪個(gè)程序執(zhí)行就讓哪個(gè)程序執(zhí)行。設(shè)備驅(qū)動(dòng)GDI和directX等都是硬件的抽象,是一個(gè)中間層,它們屏蔽了硬件的具體 細(xì)節(jié),提供了通用的操作接口。LBA(Logical Block Address):因?yàn)橛脖P結(jié)構(gòu)復(fù)雜,概念繁多,尋找一個(gè)扇區(qū) 要經(jīng)過很多步驟,這個(gè)比較麻煩。與其如此,不如干脆為每個(gè)扇區(qū)配置一個(gè)邏輯 編號(hào),這樣找扇區(qū)就好像是哈希算法一樣快。1.5內(nèi)存不夠怎么辦?程序在內(nèi)存中的地址空間是需要相互隔離的。這是為了防止一個(gè)程序在無意 間修改其他程序造成意料之外的結(jié)果,另外,這也是為了信息安全。內(nèi)存利用率要高,要不然程序在內(nèi)存和硬盤之間進(jìn)行I/O操作所花費(fèi)的時(shí)間 可

14、就多了。程序運(yùn)行的地址應(yīng)該是確定的。因?yàn)槎鄶?shù)程序指令跳轉(zhuǎn)的目標(biāo)地址是固定的,如果運(yùn)行地址不確定就不能保證每次都在目標(biāo)地址上運(yùn)行,這就需要重定向進(jìn)行調(diào)整,浪費(fèi)時(shí)間。解決上述問題的辦法是使用中間層,即把程序的運(yùn)行地址與目標(biāo)地址建立一 種映射關(guān)系。關(guān)于隔離我們平時(shí)說的什么32位,64位CPU啥的都是指CPU的處理能力,從硬件 的角度講,即,計(jì)算機(jī)的地址總線的條數(shù)。從 CPU的設(shè)計(jì)上講就是CPU 次能 夠處理的二進(jìn)制位數(shù),而這個(gè)位數(shù)還有一個(gè)學(xué)名叫字長。內(nèi)存的物理地址空間就是真實(shí)的內(nèi)存空間,虛擬地址空間則是應(yīng)用于進(jìn)程的邏輯地址空間。分段我在想如何從16進(jìn)制的差值一下推斷出地址空間的大???以下是我的想法。

15、1位16進(jìn)制數(shù)字代表4位2進(jìn)制數(shù)字,換句話說16進(jìn)制 數(shù)字轉(zhuǎn)換為2進(jìn)制數(shù)字是以24為單位進(jìn)行換算的。那么根據(jù)某個(gè)16進(jìn)制數(shù)字所 在位置乘以當(dāng)前權(quán)值就可以得到該位置上的16進(jìn)制數(shù)字所代表的2進(jìn)制數(shù)字。而16進(jìn)制某位的權(quán)值等于低一位的權(quán)值乘以 24,并且16進(jìn)制最低位的權(quán)值是 2°,因此可以根據(jù)這個(gè)規(guī)律換算出相應(yīng)的 2進(jìn)制數(shù)字。來看個(gè)例子。書上說從0X00000000到0X00A00000的地址空間大小就等于 |0x00A00000-0x00000000|=|A00000因?yàn)?A 是 10 所以其等價(jià)于 |1000000|,現(xiàn)在按 照上述規(guī)律進(jìn)行換算。10 X 220+0 >216

16、+0 >212+0 >28+0 >24+0 >2°=10M(byte)。分段的方法可以使各進(jìn)程彼此隔離,并且可以使程序運(yùn)行的地址確定。分段的缺點(diǎn)就是它以程序?yàn)閱挝贿M(jìn)行處理,但是根據(jù)程序運(yùn)行的局部性原 理,程序通常情況下只有一少部分需要常駐內(nèi)存,因此以程序?yàn)閱挝粨Q進(jìn)換出嚴(yán) 重影響了內(nèi)存的利用率和處理速度。分頁頁面有3種:1、虛擬頁;2、內(nèi)存頁;3、磁盤頁。MMU(Memory Ma nageme nt Un it)負(fù)責(zé)把虛擬地址轉(zhuǎn)換成物理地址。1.6眾人拾柴火焰高線程基礎(chǔ)使用線程的好處?1、多線程可以有效利用等待時(shí)間。因?yàn)槟尘€程陷入等待狀態(tài)后別的線程可 以繼續(xù)執(zhí)

17、行;2、多線程不會(huì)使與用戶的交互中斷。因?yàn)榭梢砸粋€(gè)線程負(fù)責(zé)與用戶交互,另一個(gè)線程負(fù)責(zé)計(jì)算;3、能夠?qū)崿F(xiàn)程序內(nèi)部并發(fā)執(zhí)行操作;4、多核CPU等硬件的潛力只有多線程才能使其充分發(fā)揮;5、在數(shù)據(jù)共享方面更高效。線程的私有存儲(chǔ)空間?1、棧;2、線程局部存儲(chǔ)(Thread Local Storage,TLS; 3、寄存器。線程真正的并發(fā)執(zhí)行和非真正并發(fā)執(zhí)行?在同一時(shí)間只有處理器核心數(shù)量大于等于執(zhí)行線程數(shù)量的時(shí)候才是真并發(fā) 執(zhí)行,除此之外都是模擬出來的。線程調(diào)度:在同一時(shí)間處理器的核心數(shù)量小于執(zhí)行線程的數(shù)量時(shí)就需要在同 一核心不斷切換來執(zhí)行線程。改變線程優(yōu)先級(jí)的3種方式1、用戶指定優(yōu)先級(jí);2、根據(jù)等待狀態(tài)

18、的頻繁程度調(diào)整優(yōu)先級(jí);3、長時(shí)間 得不到執(zhí)行而被提升優(yōu)先級(jí)??蓳屨紙?zhí)行線程和不可搶占執(zhí)行線程:線程的各種狀態(tài)完全由操作系統(tǒng)來控 制這就叫可搶占,就像某線程的時(shí)間片用完進(jìn)入就緒態(tài)一樣, 這就是由操作系統(tǒng) 來控制的。除此之外的就是不可搶占線程。不可搶占線程主動(dòng)放棄執(zhí)行的時(shí)機(jī):1、線程等待某事件發(fā)生時(shí)。2、線程主 動(dòng)放棄時(shí)間片。因?yàn)榫瓦@倆條件所以不可搶占線程調(diào)度的時(shí)機(jī)是確定的Linux下的多線程:不像 Windows那樣把線程和進(jìn)程分得那樣清楚, Linux 是以任務(wù)為單位的,如果某幾個(gè)任務(wù)的執(zhí)行是做同一件事的各個(gè)部分, 那么這幾 個(gè)任務(wù)就可以看成是線程,而這件事就可以看成是進(jìn)程。 所以Linux下

19、的線程和 進(jìn)程是動(dòng)態(tài)的概念。Linux下的fork函數(shù):fork是叉子的意思,我不知道為啥 Linux用它來給函 數(shù)命名。它的作用就是復(fù)制任務(wù),新任務(wù)和原任務(wù)共享同一塊內(nèi)存空間, 并且是 寫時(shí)復(fù)制。所謂寫時(shí)復(fù)制就是寫的時(shí)候才從內(nèi)存空間里面復(fù)制出一塊給你寫, 原 內(nèi)存空間內(nèi)容不變。讀的時(shí)候新舊任務(wù)讀同一塊內(nèi)存空間。Linux下的exec函數(shù):fork產(chǎn)生的是本任務(wù)的鏡像,也就是復(fù)制品。兩個(gè)同 樣的任務(wù)完成同樣的功能是浪費(fèi)啊,所以fork是個(gè)半成品函數(shù),必須搭配別的函數(shù)才有用,這個(gè)函數(shù)就是exec函數(shù)。Exec函數(shù)用來執(zhí)行別的可執(zhí)行文件,換 句話說就是干別的事。所以可以把fork理解成在一塊內(nèi)存空

20、間上創(chuàng)造出個(gè)接口 給exec執(zhí)行新任務(wù)。Linux下的clone函數(shù):我對(duì)它的理解就是fork和exec二合一,clone的作 用就是產(chǎn)生新線程。線程安全要知道線程安全就得知道啥叫線程不安全。所謂線程不安全就是指多個(gè)線程 同時(shí)訪問共享數(shù)據(jù)造成結(jié)果的不確定性。原子操作:絕對(duì)不會(huì)被打斷的操作。因?yàn)樵邮腔瘜W(xué)反應(yīng)中的最小微粒不可 再分所以拿這個(gè)來比擬原子操作。它適用于簡(jiǎn)單應(yīng)用環(huán)境。解決線程不安全的通用方法是鎖。線程同步:一開始我還以為是多個(gè)線程一起訪問某個(gè)資源呢,其實(shí)不然,線程同步是解決線程訪問同一數(shù)據(jù)資源的解決方式,保證了同一時(shí)間只有同一線程訪問數(shù)據(jù)資源,從而保證了線程安全。鎖一一二元信號(hào)量:最簡(jiǎn)

21、單的鎖機(jī)制。只允許一個(gè)線程獨(dú)占,一旦有線程占 用,鎖就呈現(xiàn)占用狀態(tài),其他線程無法訪問資源。否則,非占用狀態(tài),可以接受 線程。鎖一一多元信號(hào)量:就是它允許多個(gè)線程同步訪問資源,比二元信號(hào)量高能 一些。我感覺信號(hào)量就像管道。一個(gè)線程想訪問資源它就必須首先獲取一個(gè)管道, 這樣原來的管道數(shù)就少 1于是信號(hào)量首先減1。但是如果信號(hào)量減1以后成為 負(fù)值,說明原來的管道數(shù)為0,即原來就已經(jīng)沒有管道了,那么此時(shí)信號(hào)量機(jī)制 就只能讓該線程等待了,這就是 P原語。而如果一個(gè)線程用完了資源想要釋放, 那么它必須歸還它所使用的管道,那么管道總數(shù)應(yīng)該加1,即信號(hào)量加1。正因?yàn)樾盘?hào)量已經(jīng)加1,如果此時(shí)的信號(hào)量值為小于1,

22、那說明在加1之前管道總量 就已經(jīng)透支了,而且先前那些因?yàn)闆]有獲得管道的線程還在那等著呢。正好有個(gè) 線程歸還了管道,V原語趕緊從那些等待的線程中找一個(gè)出來把管道給它,這就是在信號(hào)量值小于1的情況下喚醒線程的意思。鎖一一互斥量(Mutex):信號(hào)量與互斥量的區(qū)別是一個(gè)信號(hào)量可以被一個(gè)線 程獲取并釋放給另一個(gè)線程使用,正如 V原語的操作。而互斥量始終都是一個(gè) 線程,上鎖是這個(gè)線程,這個(gè)線程不執(zhí)行完就不解鎖。鎖一一臨界區(qū):獲取臨界區(qū)的鎖為進(jìn)入臨界區(qū),釋放鎖為離開臨界區(qū)。它的 作用對(duì)象是某一位以進(jìn)程,一旦某進(jìn)程進(jìn)入臨界區(qū),其他進(jìn)程就無法進(jìn)入。除此 之外,臨界區(qū)與互斥量相同。鎖一一讀寫鎖:互斥量、臨界區(qū)和

23、信號(hào)量適用于讀寫都非常頻繁的場(chǎng)合,而讀寫鎖適用于讀頻繁而寫不頻繁的場(chǎng)合。它的工作規(guī)律可用下表表示:鎖寫鎖狀態(tài)以共享方式獲取以獨(dú)占方式獲取自由成功成功卄享/、成功等待獨(dú)占等待等待鎖一一條件變量:相當(dāng)于一個(gè)開關(guān),它可以讓等待它的線程繼續(xù)等待也可以 讓它們繼續(xù)執(zhí)行。而這個(gè)開關(guān)需要一些其他的線程打開或關(guān)閉它??芍厝牒瘮?shù):一個(gè)函數(shù)沒有執(zhí)行完全,但是由于內(nèi)部因素或者外部調(diào)用, 又 一次開始執(zhí)行該函數(shù)。它不產(chǎn)生任何不良后果。產(chǎn)生可重入的條件:1多線程共同執(zhí)行該函數(shù)。2、函數(shù)自己直接或者間接 調(diào)用自身??芍厝牒瘮?shù)的特點(diǎn):1不使用任何(局部)靜態(tài)或全局的非 const變量。 因?yàn)槿绻褂玫脑捤蜕嫦硬倏v共享數(shù)據(jù)

24、,這樣會(huì)導(dǎo)致線程不安全。2、不返回任何(局部)靜態(tài)或全局的非const變量的指針。因?yàn)檫@同樣涉及到共享數(shù)據(jù)。3、僅依賴于調(diào)用方提供的參數(shù)。因?yàn)檫@樣可以把函數(shù)的執(zhí)行過程局限在局部。4、 不依賴于任何單個(gè)資源的鎖。單個(gè)資源的鎖不允許被中斷,這不符合可重入函數(shù) 的定義。5、不調(diào)用任何不可重入函數(shù)。這個(gè)沒啥好說的,如果調(diào)用了,可重入 函數(shù)就成了不可重入函數(shù)??芍厝胄再|(zhì)是并發(fā)安全的強(qiáng)力保證可在多線程環(huán)境下 大膽使用。過度優(yōu)化:P53這個(gè)例子就是說本來2個(gè)x+結(jié)果是2,但是經(jīng)過上鎖以后卻是1,這證明即使通過鎖機(jī)制也不能完全保障計(jì)算正確,這是計(jì)算機(jī)內(nèi)部工作 機(jī)制造成的線程不安全。CPU對(duì)程序的優(yōu)化可能導(dǎo)致線

25、程不安全,因?yàn)樗鼤?huì)調(diào)整程序語句執(zhí)行順序 以達(dá)到CPU所謂的優(yōu)化,這有時(shí)候很麻煩。Volatile關(guān)鍵字可以阻止這種優(yōu)化。1、它阻止編譯器為提高程序執(zhí)行速度將一個(gè)變量緩存到寄存器內(nèi)而不寫回。2、它阻止編譯器調(diào)整語句執(zhí)行順序。這兩件事就是volatile所做的具體工作。但是, volatile能管住編譯器管不了 CPU,CPU還是能對(duì)指令進(jìn)行動(dòng)態(tài)調(diào)整。P54舉了一個(gè)double-check的例子,雖然現(xiàn)在我對(duì)這個(gè)沒有多深的理解,但 是從這個(gè)例子中我看到作者是怎么分析的。它是將各個(gè)語句內(nèi)部實(shí)際所進(jìn)行的操 作都列出來進(jìn)行分析的,這個(gè)值得我學(xué)習(xí)。雖然volatile管不了 CPU,但是CPU有CPU相當(dāng)

26、于volatile的指令,一般這個(gè)指令叫做barrier。163多線程內(nèi)部情況線程分為內(nèi)核級(jí)線程和用戶級(jí)線程,內(nèi)核級(jí)線程是用戶直接接觸不到的,用 戶只能接觸到用戶級(jí)線程。3種內(nèi)核級(jí)線程與用戶級(jí)線程的模型。1、一對(duì)一模型:就是每個(gè)用戶級(jí)線程都對(duì)應(yīng)一個(gè)內(nèi)核級(jí)線程,但反過來不是,因?yàn)閮?nèi)核級(jí)線程可能沒有用戶級(jí)線程與之對(duì)應(yīng)。一般直接使用API或者系統(tǒng)調(diào)用創(chuàng)建的線程均為一對(duì)一模型。它的優(yōu)點(diǎn):真正實(shí)現(xiàn)線程的并發(fā)執(zhí)行,線程之間彼此互不影響。它的缺點(diǎn):1、許多操作系統(tǒng)限制了內(nèi)核級(jí)線程的數(shù)量導(dǎo)致用戶級(jí)線程數(shù)量 受限。2、許多操作系統(tǒng)用在內(nèi)核級(jí)線程調(diào)度上的開銷較大,主要為上下文切換 開銷,致使用戶級(jí)線程執(zhí)行效率低下

27、。2、多對(duì)一模型:多個(gè)用戶級(jí)線程對(duì)應(yīng)同一個(gè)內(nèi)核級(jí)線程,線程的切換由用戶級(jí)代碼決定。作者說多處理器對(duì)提升處理速度沒有明顯幫助,這是當(dāng)然的了, CPU處理的是內(nèi)核級(jí)線程,而這個(gè)模型就在那擺著,CPU也只能按照這個(gè)模式來處理。再說了,一個(gè)線程只能在一個(gè)核上跑,你再多給幾個(gè)核也沒用啊。它的優(yōu)點(diǎn):它比一對(duì)一模型快,還有高效的上下文切換和近似無限制的線程 數(shù)量。它的缺點(diǎn):只要有一個(gè)線程阻塞,對(duì)應(yīng)于同一個(gè)內(nèi)核級(jí)線程的其他線程也無 法執(zhí)行,該內(nèi)核級(jí)線程也阻塞,這很好理解,因?yàn)橹挥幸粭l通路。3、多對(duì)多模型:是上面二者的合體。很顯然它能克服上述二者的缺點(diǎn),同 理多處理器也無法顯著提升它的執(zhí)行效率。第二章編譯和鏈接

28、2.1被隱藏了的過程以前學(xué)的程序的執(zhí)行過程是編輯、編譯、鏈接、執(zhí)行。今天這本書把這個(gè)過 程更加細(xì)化了,它以C語言中的helloworld程序?yàn)槔M(jìn)行說明,講的大概是從編 譯到鏈接的過程。也是包括4步:1、預(yù)處理;2、編譯;3、匯編;4、鏈接。從這個(gè)順序可以 看出在C語言中預(yù)處理是在編譯之前。預(yù)編譯預(yù)編譯是個(gè)獨(dú)立的過程,不同于源文件的.cpp格式和頭文件的.h格式,預(yù)編 譯得到的文件后綴是.i或者.ii。預(yù)編譯的主要?jiǎng)幼骶褪翘幚泶a中以#開頭的指令,具體可見P64這些步驟。 因?yàn)楹暌呀?jīng)展開所以.i文件不包含任何宏定義??梢愿鶕?jù).i文件查看宏定義和文 件包含是否正確。預(yù)編譯需要預(yù)編譯器。編譯編譯的

29、過程是把預(yù)處理得到的文件進(jìn)行詞法分析、語法分析、語義分析和優(yōu) 化后生成相應(yīng)的匯編代碼文件。匯編匯編階段是通過匯編器完成的,其作用就是把匯編指令轉(zhuǎn)換成機(jī)器指令。匯 編結(jié)束以后生成目標(biāo)文件.obj鏈接鏈接簡(jiǎn)而言之就是把目標(biāo)文件鏈接在一起生成可執(zhí)行文件的過程,但是實(shí)際上這是一個(gè)非常復(fù)雜的過程,并不像看上去那么簡(jiǎn)單。2.2編譯器做了什么編譯的過程可以分為掃描、語法分析、語義分析、源代碼優(yōu)化、代碼生成、 目標(biāo)代碼優(yōu)化等6步。詞法分析這一過程是交給掃描器執(zhí)行的,目的是把程序語句劃分成若干記號(hào)。這些記號(hào)一般包括:1、關(guān)鍵字;2、標(biāo)識(shí)符;3、字面量(數(shù)字,字符串等);4、特殊符號(hào)(加號(hào),等號(hào)等)。此外,掃描器

30、還將標(biāo)識(shí)符放到符號(hào)表,將字面量放到文字表中以備后用。詞法分析需要此法掃描器。語法分析它是對(duì)詞法分析產(chǎn)生的各種記號(hào)進(jìn)行語法分析,并產(chǎn)生一顆語法樹語句內(nèi)容含義的區(qū)分,語法的檢查等都是在此階段完成的。語法分析需要語法分析器。語義分析語義分析需要語義分析器。語義分析就是分析該語句的意思,就是它能做什么,有啥用。編譯器所能做的包括靜態(tài)語義分析和動(dòng)態(tài)語義分析。靜態(tài)語義:編譯期能夠確定的語義,它主要包括類型和聲明的匹配,類型的 轉(zhuǎn)換等。我想C+中的靜態(tài)綁定應(yīng)該也屬于靜態(tài)語義吧。動(dòng)態(tài)語義:運(yùn)行期能夠確定的語義以及相關(guān)問題, 比如說異常處理。我同時(shí) 在想C+中的動(dòng)態(tài)綁定應(yīng)該屬于動(dòng)態(tài)語義。語義分析對(duì)語法樹各節(jié)點(diǎn)進(jìn)

31、行了類型標(biāo)記和類型轉(zhuǎn)換,還更新了符號(hào)表里的 符號(hào)類型。224中間語言生成編譯器有很多層次的優(yōu)化,源碼級(jí)別的優(yōu)化是其中一個(gè)層次。源碼級(jí)的優(yōu)化需要源碼級(jí)優(yōu)化器。這個(gè)優(yōu)化是把語法樹轉(zhuǎn)換成中間代碼,并在中間代碼上進(jìn)行的。常見的中間代碼有三地址碼和 P代碼。中間代碼將編譯器分成了前端和后端,前端負(fù)責(zé)產(chǎn)生與機(jī)器無關(guān)的中間代 碼,后端負(fù)責(zé)把中間代碼轉(zhuǎn)換成目標(biāo)代碼。跨平臺(tái)的編譯器并不是放在任意一個(gè)平臺(tái)上都絕對(duì)能用,只不過它能支持的 平臺(tái)很多而已。這是因?yàn)榫幾g器使用同一個(gè)前端,而針對(duì)不同的平臺(tái)使用不同的 后端。目標(biāo)代碼的生成與優(yōu)化編譯器的后端包括代碼生成器和目標(biāo)代碼優(yōu)化器。代碼生成器將中間代碼轉(zhuǎn)換成目標(biāo)代碼,該

32、過程依賴于目標(biāo)機(jī)器。目標(biāo)代碼優(yōu)化器對(duì)目標(biāo)代碼進(jìn)行優(yōu)化, 比如選擇合適的尋址方式,以移位代 替數(shù)乘等。現(xiàn)在的編譯器非常復(fù)雜,上述提到的這些方面也變得非常復(fù)雜。變量和函數(shù)的地址都是在最終鏈接的時(shí)候才確定的,然后變成可執(zhí)行文件。2.3鏈接器年齡比編譯器長作者把鏈接比喻為拼圖的拼接。2.4模塊拼接一一靜態(tài)鏈接將源代碼模塊組裝起來的過程就是鏈接。鏈接的過程包括:1、地址和空間分配;2、符號(hào)決議;3、重定位等。.obj文件即目標(biāo)文件和庫一起鏈接成可執(zhí)行文件。庫是由一些常用的代碼編譯成的目標(biāo)文件的包, 是一個(gè)集合。最常見的庫是 運(yùn)行時(shí)庫,是支持程序運(yùn)行的基本函數(shù)的集合。每個(gè)目標(biāo)文件都是單獨(dú)編譯的。模塊A想要

33、調(diào)用模塊B的C函數(shù),A必須要知道C的地址,但是現(xiàn)在A不 知道C的地址,但是A給C留了位置,等到鏈接器鏈接時(shí)再在這個(gè)位置上填上 C的地址。如果C的地址被改動(dòng)了, A中所有調(diào)用C的地方都需要進(jìn)行相應(yīng)的 更改,這些都可藉由鏈接器完成。這是靜態(tài)鏈接的基本功能和作用。在鏈接的過程中需要對(duì)目標(biāo)文件中定義在其他目標(biāo)文件中的函數(shù)和變量的調(diào)用指令進(jìn)行重新調(diào)整,注意這里說的是指令!書中舉的例子意在說明,當(dāng)目標(biāo) 文件A調(diào)用目標(biāo)文件B中的變量C時(shí),因?yàn)闀簳r(shí)無法知道C的位置,所以指令 先把表示C的位置置為某一值,等到鏈接的時(shí)候再把這值修正為 C的地址,這 一過程叫做重定位,像C這樣的位置被稱為重定位入口。第三章目標(biāo)文件

34、里有什么.obj是目標(biāo)文件,所以可以知道目標(biāo)文件是指編譯后生成的文件,目標(biāo)文件幾乎和可執(zhí)行文件相同只是稍微有點(diǎn)不同而已。其不同之處在于有些符號(hào)和地址沒有被調(diào)整。3.1目標(biāo)文件的格式正是因?yàn)槟繕?biāo)文件與可執(zhí)行文件幾乎相同,所以它們的存儲(chǔ)格式是一樣的, 可以把它們近似看成同一種文件。Linux下的動(dòng)態(tài)鏈接庫格式為.so,Windows和Linux下的靜態(tài)鏈接庫格式分 別為.lib和a靜態(tài)鏈接庫是一個(gè)文件,該文件包含了很多目標(biāo)文件,它是一個(gè)整體。Linux下的可執(zhí)行文件是按照ELF格式存儲(chǔ)的,ELF標(biāo)準(zhǔn)包含4種文件,請(qǐng) 看P81。我所熟悉的 Windows下的DLL就屬于共享目標(biāo)文件。3.2目標(biāo)文件是

35、什么樣的目標(biāo)文件一般包含了哪些內(nèi)容?編譯后的機(jī)器指令代碼、數(shù)據(jù)、連接所需的信息、符號(hào)表、調(diào)試信息、字符串等。目標(biāo)文件把信息按照屬性的不同分段存儲(chǔ)。寫到這里我感覺這書上說的與老 師課上講的程序在內(nèi)存中的分段方法有些相似。在目標(biāo)文件中,編譯后的機(jī)器指令代碼放在代碼段(Code Section)中,段名一般為.code和.text。全局變量和靜 態(tài)變量放在數(shù)據(jù)段(Data Section)中,段名一般為.data。BSS段(Block Started By Symbo I)用來存儲(chǔ)未初始化的靜態(tài)變量和全局變量。話雖如此bss中并沒有這些變量的內(nèi)容,它只是為這些變量按照所占空間大小預(yù) 留空間而已。由于

36、這些變量默認(rèn)就是 0,所以壓根沒必要再為它們分配一個(gè)數(shù)據(jù)0,也沒有必要讓它們待在data段中。因此bss的作用是為這些變量預(yù)留空間。另外目標(biāo)代碼還有一個(gè)文件頭用來保存該目標(biāo)文件的信息,它里面還有一個(gè)段表。源代碼被編譯以后生成兩種段數(shù)據(jù)段和指令段,.code.text屬于指令 段.data.bss屬于數(shù)據(jù)段。這樣分主要有3點(diǎn)好處:1、防止程序被有意無意篡改。這是因?yàn)橹噶疃沃蛔x,數(shù)據(jù)段可讀寫。2、提高了緩存命中率。3、節(jié)省內(nèi)存空間。因?yàn)橹噶疃慰杀欢鄠€(gè)副本共享,但是副本可以擁有自己 的數(shù)據(jù)段。3.3 挖掘 SimpleSection.o原來目標(biāo)文件中的段還有只讀數(shù)據(jù)段(.rodata)、注釋信息段(

37、.comment)、 堆棧提示段(.note.GNU-stack)。從書中所給的例子來看一個(gè)ELF文件只有4個(gè)段是由內(nèi)容的, 即.data .text、.rodata .comment。從圖3-3可以看出在內(nèi)存中,從低地址到高地址是按照ELF header text、data rodata comment、other data的順序存放的。3.3.3 BSS段由本小節(jié)可知,全局變量可能因?yàn)檎Z言和編譯器的不同不一定存放在bss段,但是靜態(tài)變量一定存放在bss段。雖說bss存放的是未初始化的靜態(tài)和全局變量,但是有些變量如果被初始化為0,它也會(huì)被放在bss中,這是編譯器的優(yōu)化,有時(shí)候這種優(yōu)化會(huì)帶來麻

38、煩。其他段表3-2列出了其他段及意義。此外,這個(gè)段還可以自定義。3.4 ELF文件結(jié)構(gòu)描述圖3-4展示了 ELF的層次結(jié)構(gòu)。最重要的兩個(gè)部分就是ELF文件頭和段表。ELF文件頭描述整個(gè)文件的基 本屬性,段表描述各段的信息。文件頭清單3-2清楚地描述了 ELF文件頭的信息,P95黑體部分列舉了 ELF文件 頭包含的信息。ELF文件兼容各平臺(tái),它的文件結(jié)構(gòu)和相關(guān)參數(shù)定義在” /usr/include/elf.h里, 它有32位和64位兩種。表3-3展示了 elf.h的自定義變量體系。表3-4展示了 ELF文件頭結(jié)構(gòu)成員含義。ELF魔數(shù):ELF文件頭的第一個(gè)字段是Magic,包含16bytes,對(duì)應(yīng)

39、于Elf32_Ehdr中的e_ident成員。Magic用來表示平臺(tái)的各種屬性。14個(gè)字節(jié)是所有ELF文件都相同的標(biāo)識(shí)碼,分別對(duì)應(yīng) del、E、L、F,這 四個(gè)字節(jié)就是ELF魔數(shù)。操作系統(tǒng)通過確認(rèn)魔術(shù)是否正確以決定是否加載可執(zhí) 行文件。第5個(gè)字節(jié)用來表示ELF文件是32位的還是64位的。第6個(gè)字節(jié)用來表示ELF字節(jié)序。第7個(gè)字節(jié)用來表示ELF文件版本號(hào)。后面的9個(gè)字節(jié)用來預(yù)留,有些平臺(tái)可能用來作為擴(kuò)展標(biāo)志。Elf32_Ehdr中的e_type成員表示ELF文件類型,ELF總共有三種文件類型 如表3-5所示。操作系統(tǒng)是通過判斷文件類型而不是擴(kuò)展名來確定 ELF文件類 型的。Elf32_Ehdr中

40、的e_machine成員表示ELF文件的平臺(tái)屬性。雖然 ELF遵循 統(tǒng)一標(biāo)準(zhǔn)但不代表同一 ELF文件可以在不同平臺(tái)上使用。段表它用來表示各個(gè)段的信息,ELF文件中的段是由段表決定的。一個(gè)ELF文件不僅僅包含像data text、bss這樣的段,還包括其他的輔助性段。段表是一個(gè)Elf32_Shdr類型的結(jié)構(gòu)體數(shù)組,元素的個(gè)數(shù)代表段的個(gè)數(shù),每 個(gè)元素對(duì)應(yīng)一個(gè)段。這個(gè)Elf32_Shdr被稱為段描述符。表3-7描述了 Elf32_Shdr中各字段的意義。段的名稱對(duì)于編譯和鏈接有意義,對(duì)操作系統(tǒng)無意義。決定段的類型的是段 的類型字段,并不是段的后綴名和名稱。段的類型和段的標(biāo)志位字段決定了段的屬性。表3

41、-8展示了段的各種類型。段的標(biāo)志位表示該段在進(jìn)程虛擬地址空間中的屬性,如是否可讀。表3-9列出了段的各種屬性。表3-10列出了系統(tǒng)保留段的各種屬性。段的連接信息包括sh_link和sh_info,它們與鏈接相關(guān),如表3-11所示。重定位表目標(biāo)文件中有一個(gè)SHT_REL的.rel.text字段,它是重定位表。重定位發(fā)生在連接的過程中,這個(gè)在前面已經(jīng)講過,重定位表記錄了重定位相關(guān)信息。字符串表顧名思義,就是用來表示各種名稱的字符串的表。它是一個(gè)裝有各種字符串的表格,每個(gè)字符在表中都有一個(gè)固定的位置。這種表在ELF文件中保存為2種形式一一.strtab和.shstrtab,它們分別是字 符串表和段字

42、符串表,它們?cè)贓LF文件中都以獨(dú)立的段而存在。為了輕松地找到這個(gè)段,在ELF文件頭中包含了這兩個(gè)段的下標(biāo),名為e_shstrndx)3.5鏈接的接口符號(hào)鏈接是組合目標(biāo)文件的過程,目標(biāo)文件是根據(jù)彼此之間的地址相互引用, 從 而組合成可執(zhí)行文件的。而,這個(gè)地址可以簡(jiǎn)單地理解為目標(biāo)文件中的函數(shù)和變 量。在這里,函數(shù)和變量統(tǒng)稱為符號(hào),函數(shù)名和變量名統(tǒng)稱為符號(hào)名。鏈接器的著眼點(diǎn)主要在定義在本目標(biāo)文件和定義在其他目標(biāo)文件的全局性 符號(hào),因?yàn)橹挥羞@些涉及到目標(biāo)文件之間的組合。3.5.1 ELF符號(hào)表結(jié)構(gòu)ELF文件的符號(hào)表是一個(gè)段,段名為“ .symtab”它是一個(gè)Elf32_sym類型 的數(shù)組,每個(gè)數(shù)組元素

43、代表一個(gè)符號(hào)。在Elf32_sym結(jié)構(gòu)體中有一個(gè)32bit成員叫st_info,低4bit表示符號(hào)的類型,高28bit符號(hào)的綁定信息。綁定信息具體可見表3-15,符號(hào)類型可參見表3-16。Elf32_sym.st_shndx:如果符號(hào)定義在本目標(biāo)文件中,它表示該符號(hào)所在的 段在段表中的下標(biāo),否則它具有其他意義。st_shndx具體信息可見表3-17。Elf32_sym.st_value:每個(gè)符號(hào)都有一個(gè)對(duì)應(yīng)值,它一般為變量和函數(shù)的地址。 st_value的意義有如下幾種:1、如果符號(hào)定義在目標(biāo)文件中,并且它不是COMMON 塊類型, 則st_value代表符號(hào)在段中的偏移。2、 如果符號(hào)定義在

44、目標(biāo)文件中并且是COMMON塊類型,則st_value表示符號(hào)的對(duì)齊屬性。3、在可執(zhí)行文件中st_value表示符號(hào)的虛擬地址。特殊符號(hào)鏈接器本身自帶的,不是你定義的,定義在鏈接腳本中的,但是你可以用的, 這樣的符號(hào)是特殊符號(hào)。它們存在的時(shí)機(jī)是鏈接器鏈接生成可執(zhí)行文件時(shí),此時(shí) 鏈接器會(huì)將它們解析成正確的值,書中P110舉了幾個(gè)具有代表性的特殊符號(hào)。符號(hào)修飾與函數(shù)簽名本小節(jié)明確了函數(shù)簽名的概念。函數(shù)簽名:主要是指函數(shù)名和參數(shù)類型,其次是所在類和命名空間等。它用 于區(qū)分不同函數(shù)。編譯器和連接器會(huì)使用名稱修飾的辦法加工函數(shù)簽名使之成為修飾后名稱, 在C+中為符號(hào)名。不同的編譯器對(duì)函數(shù)簽名的修飾方法不

45、同, 這導(dǎo)致不同種類的目標(biāo)文件無法 互連。原來C+編譯器已經(jīng)默認(rèn)定義了宏 cplusplus來兼容C語言和C+。弱符號(hào)和強(qiáng)符號(hào)在不同目標(biāo)文件中含有相同全局性符號(hào)定義,這種情況被稱為強(qiáng)符號(hào),它會(huì)引起符號(hào)重定義。C/C+編譯器認(rèn)為未初始化的全局變量是弱符號(hào)。這個(gè)強(qiáng)弱符號(hào)是可以被定義的,所以強(qiáng)弱之別是根據(jù)定義來劃分的, 并不針 對(duì)符號(hào)的引用,P117代碼說明了這一點(diǎn)。鏈接器根據(jù)符號(hào)的強(qiáng)弱來處理和選擇定義的全局變量:1、不允許多次定義強(qiáng)符號(hào),否則報(bào)錯(cuò)。2、同一個(gè)符號(hào)在各目標(biāo)文件中出現(xiàn)了多次,但只有一個(gè)是強(qiáng)符號(hào),那么編 譯器選擇強(qiáng)符號(hào)的那個(gè)。3、如果一個(gè)符號(hào)在所有目標(biāo)文件中都是弱符號(hào),那么編譯器選擇占用

46、空間最大的一個(gè)。由此可見編譯器對(duì)于弱符號(hào)的選擇并不明顯,所以由弱符號(hào)造成的錯(cuò)誤也相對(duì)難以發(fā)現(xiàn)。強(qiáng)引用:目標(biāo)文件對(duì)于非本目標(biāo)文件的符號(hào)引用, 在鏈接成可執(zhí)行文件的過 程中,如果找不到該符號(hào)的定義,就報(bào)未定義錯(cuò)誤。弱引用:與強(qiáng)引用差不多,只不過在找不到符號(hào)時(shí)不報(bào)錯(cuò)。強(qiáng)弱引用主要用于庫的鏈接。對(duì)于未定義的弱引用,編譯器為便于識(shí)別把它 看作是某一值,一般為0。弱符號(hào)與COMMON塊聯(lián)系較密切。弱引用是可以手動(dòng)聲明的,如 P118第一段代碼所示。弱符號(hào)的作用在于提供一個(gè)默認(rèn)的庫符號(hào), 但是當(dāng)用戶想要自定義該符號(hào)的 時(shí)候,該自定義符號(hào)就獲得了更高的優(yōu)先級(jí)。 而弱引用的作用在于增強(qiáng)了程序的 可擴(kuò)展性,因?yàn)橛?/p>

47、了弱引用程序功能更強(qiáng),沒有弱引用程序也能正常運(yùn)行。3.6調(diào)試信息目標(biāo)文件和可執(zhí)行文件中都可能保存調(diào)試信息,ELF文件采用DWARF格式 保存調(diào)試信息。由于調(diào)試信息與可執(zhí)行文件最終結(jié)果無關(guān), 而且占用大量空間,所以在發(fā)布 軟件時(shí)應(yīng)該去掉這些調(diào)試信息。第4章靜態(tài)鏈接靜態(tài)鏈接是指將目標(biāo)文件鏈接在一起形成可執(zhí)行文件的過程。4.1空間與地址分配相似段合并靜態(tài)鏈接過程是把各目標(biāo)文件中的各段合并到可執(zhí)行文件中的相應(yīng)段中。鏈接器為目標(biāo)文件分配地址和空間。 這個(gè)空間有兩層含義,既包括在可執(zhí)行 文件中占有的空間也包括在虛擬地址中分配的空間。其中虛擬地址空間的分配關(guān) 系重大。靜態(tài)鏈接的過程一般分兩步一一1、空間與地

48、址分配。2、符號(hào)解析與重定位。第一步就是獲取段信息,合并段將它們映射到可執(zhí)行文件的段表信息中。 整 理符號(hào)和引用并放入全局符號(hào)表中。第二步,實(shí)際上就是鏈接,把目標(biāo)文件中的地址呀、符號(hào)呀、數(shù)據(jù)等進(jìn)行重 定位然后鏈接。VMA:Virtual Memory AddressLMA : Load Memory Address鏈接前的VMA都是0,鏈接后就有實(shí)實(shí)在在的地址了。符號(hào)地址的確定符號(hào)地址在原來的目標(biāo)文件中的每個(gè)段中都有一個(gè)偏移量,這個(gè)偏移量是固定的,所以在鏈接的過程中只要在虛擬地址的基礎(chǔ)上再加上這個(gè)偏移量就是某符 號(hào)在虛擬地址空間中的地址。4.2符號(hào)解析與重定位在空間和地址分配完成以后,鏈接器即

49、將進(jìn)行符號(hào)解析與重定位。本小節(jié)舉 了個(gè)例子,用了很多匯編代碼,有些晦澀難懂。目標(biāo)文件中使用的都是虛擬地址不是物理地址,這一點(diǎn)很重要。目標(biāo)文件的起始地址都是0。重定位表它存儲(chǔ)著與重定位相關(guān)的信息每個(gè)要被重定位的ELF段都對(duì)應(yīng)一個(gè)重定位表,重定位表本身也是一個(gè)段, 所以你也可以叫重定位表為重定位段。每一個(gè)要被重定位的地方叫做重定位入口。重定位入口的偏移表示入口在要被重定位的段中的位置。重定位表的實(shí)質(zhì)是一個(gè)Elf32_Rel的結(jié)構(gòu)體數(shù)組,每個(gè)數(shù)組元素對(duì)應(yīng)一個(gè)重 定位入口423符號(hào)解析重定位的過程伴隨著符號(hào)解析的過程。每個(gè)重定位的入口對(duì)應(yīng)一個(gè)符號(hào)引用,鏈接器會(huì)查找有所有目標(biāo)文件的符號(hào) 表所組成的全局符

50、號(hào)表,然后根據(jù)這個(gè)全局符號(hào)表進(jìn)行重定位。指令修正方式32位x86平臺(tái)下的ELF文件的重定位入口所修正的指令尋址方式只有2種:絕對(duì)近址32位尋址和相對(duì)近址32位尋址。修正的位置長度為4byte&經(jīng)過絕對(duì)地址修正方式修正得到的地址是該符號(hào)的實(shí)際地址,而相對(duì)地址尋址方式得到的是符號(hào)與被修正位置的距離。4.3 COMMON 塊相同的符號(hào)定義在多個(gè)不同的目標(biāo)文件中, 但是類型各不相同,這說明它們 不是同一個(gè)變量或者函數(shù),因此不能對(duì)它們進(jìn)行相同的操作。但是鏈接器只認(rèn)符 號(hào)不認(rèn)類型,它認(rèn)為它們都一樣。這種情況主要分為3種:1、至少2個(gè)強(qiáng)符號(hào)類型不一致。2、一個(gè)強(qiáng)符號(hào)和多個(gè)弱符號(hào)類型不一致。3、至少2

51、個(gè)弱符號(hào)類型不一致。強(qiáng)符號(hào)是指定義在目標(biāo)文件中全局性符號(hào), 包括函數(shù)和變量,顯然它們?nèi)绻?有相同的多個(gè),那就是重定義,這本身就會(huì)報(bào)錯(cuò)?,F(xiàn)在的編譯器和鏈接器都支持 COMMON塊機(jī)制。它主要針對(duì)的對(duì)象是弱 符號(hào)。如果在眾多符號(hào)之中有一個(gè)符號(hào)是強(qiáng)符號(hào),那么符號(hào)所占空間與強(qiáng)符號(hào)相 同。如果弱符號(hào)大小超過強(qiáng)符號(hào),編譯器會(huì)發(fā)出警告。編譯器為什么不把未初始化的全局變量當(dāng)做未初始化的局部靜態(tài)變量處理?為什么不在bss中給它們分配空間,而非要把它們標(biāo)記為 COMMON類型 呢?因?yàn)榫幾g時(shí)編譯器不知道弱符號(hào)需要多大空間,所以這時(shí)無法為其在BSS中分配空間,只能當(dāng)做局部靜態(tài)變量處理。 但是在鏈接的時(shí)候可以確定,所

52、以鏈 接以后才在BSS中分配空間。編譯器把所有未初始化的全局變量都當(dāng)成 COMMON類型處理,這樣做是 為了與強(qiáng)類型分開,凡是非 COMMON類型的都是強(qiáng)類型。多個(gè)強(qiáng)類型的符號(hào) 會(huì)發(fā)生重復(fù)定義的錯(cuò)誤。重復(fù)代碼消除C+在很多時(shí)候會(huì)產(chǎn)生重復(fù)代碼,模版是其中最具代表性的一個(gè)。模版可以 在不同的編譯單元被實(shí)例化成相同的類型,兩個(gè)完全一樣的類是完全沒有必要 的,一個(gè)足矣。不解決代碼重復(fù)問題會(huì)導(dǎo)致:1、空間浪費(fèi)。這個(gè)根本就不用解釋。2、地址容易出錯(cuò)。因?yàn)槭嵌鄠€(gè)相同的實(shí)例嘛,就會(huì)有多個(gè)指針分別指向這些實(shí)例,但是這些實(shí)例之間沒差別,它們?cè)谶壿嬌鲜峭缓瘮?shù),這就容易造成指 針的誤指。3、指令運(yùn)行效率較低。緩存機(jī)

53、制會(huì)緩存多份重復(fù)的代碼,但是程序只會(huì)用 特定的一份,在這么多份相同的代碼中找特定的一份不好找,成功率較低,即, 緩存命中率低。解決方案:把每個(gè)編譯單元中的每個(gè)模版的不同實(shí)例分別放進(jìn)不同的段中, 并且對(duì)不同的單元都這樣做,這樣在最后鏈接的時(shí)候不同編譯單元中的相同實(shí)例 段就合并從而消除多份相同的實(shí)例。缺點(diǎn):不同的編譯單元可能使用了不同的編譯器版本或者優(yōu)化選項(xiàng),這會(huì)導(dǎo) 致實(shí)際產(chǎn)生的代碼不同,鏈接器必須選擇其中一個(gè)副本。函數(shù)級(jí)別鏈接:默認(rèn)情況下鏈接器會(huì)把所有的目標(biāo)文件鏈接在一起,不管有用的代碼還是沒用的代碼,這會(huì)導(dǎo)致可執(zhí)行文件很大。所謂函數(shù)級(jí)別鏈接就是每個(gè)編譯單元也把函數(shù)單獨(dú)放進(jìn)一個(gè)段中,在鏈接的時(shí)候

54、只鏈接那些有用的函數(shù)段。這種做法會(huì)減慢編譯和鏈接的過程,因?yàn)槎蔚臄?shù)量增加了。442全局構(gòu)造與析構(gòu)在C+中全局對(duì)象的構(gòu)造在main之前完成,析構(gòu)在main之后完成。在ELF文件中有.init和.fini兩個(gè)段。init段包含了進(jìn)程的初始化代碼,在 main之前執(zhí)行。fini段包含了進(jìn)程的終止代碼,在 main之后執(zhí)行。C+的全局構(gòu)造和析構(gòu)由此實(shí)現(xiàn)。4.4.3 C+與 ABI把不同編譯器產(chǎn)生的目標(biāo)文件鏈接在一起需要特定的條件一一相同的ABI(Applicati on Binary In terface)。ABI :符號(hào)修飾標(biāo)準(zhǔn)、變量內(nèi)存布局、函數(shù)調(diào)用方式等與二進(jìn)制兼容性相關(guān) 的內(nèi)容。C語言間的目標(biāo)

55、文件能否互相兼容具體決定于如下幾個(gè)方面:1、內(nèi)置類型大小和存儲(chǔ)方式。2、組合類型大小和存儲(chǔ)方式。3、外部符號(hào)與用戶定義的符號(hào)之間的命名方式和解析方式。4、函數(shù)調(diào)用方式。5、堆棧分布方式。6寄存器使用方式。C+在這方面的決定因素 P141+P142介紹。C+代碼不僅對(duì)于由不同編譯器編譯得到的目標(biāo)文件不兼容,而且就算是同一編譯器的不同版本編譯得到的目標(biāo)文件也不兼容。這都是ABI鬧的。4.5靜態(tài)庫鏈接開發(fā)環(huán)境往往附帶語言庫,這些庫是對(duì)系統(tǒng) API的封裝。大部分的C語言 庫函數(shù)都調(diào)用了系統(tǒng)API,少數(shù)除外。靜態(tài)庫實(shí)際上可以看成是一組目標(biāo)文件的集合。C語言中看似簡(jiǎn)單的庫函數(shù)和系統(tǒng)中眾多的 API存在著依賴關(guān)系。靜態(tài)鏈接的過程分為三步:1、調(diào)用C語言

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論