程序員的自我修養(yǎng)總結(jié)_第1頁
程序員的自我修養(yǎng)總結(jié)_第2頁
程序員的自我修養(yǎng)總結(jié)_第3頁
已閱讀5頁,還剩46頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

1、目錄第一章溫故而知新 6第二節(jié)萬變不離其宗6第3節(jié) 站得高看得遠 7第4節(jié)操作系統(tǒng)的功能7不要讓CPU打盹7設(shè)備驅(qū)動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預編譯152.1.2 編譯152.1.3 匯編152.1.4 鏈接162.2編譯器做了什么16詞法分析16語法分析16語義分析16中間語言生成 17目標代碼的生成與優(yōu)化 172.3鏈接器年齡比編譯器長 182.4模塊拼接一一靜態(tài)鏈接 18第三章目標文件里有什么 183.1目標文件的格式19

2、3.2目標文件是什么樣的 193.3 挖掘 SimpleSection.o 203.3.3 BSS段 20其他段203.4 ELF文件結(jié)構(gòu)描述 20文件頭213.4.2 段表21重定位表 22字符串表 223.5鏈接的接口 一一符號 223.5.1 ELF符號表結(jié)構(gòu) 23特殊符號 23符號修飾與函數(shù)簽名 24弱符號和強符號 243.6調(diào)試信息25第4章靜態(tài)鏈接254.1空間與地址分配25相似段合并25符號地址的確定 264.2符號解析與重定位 26重定位表26符號解析 27指令修正方式 274.3 COMMON 塊27重復代碼消除 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鏈接腳本語法簡介314.7 BFD 庫31第 5 章 WINDOWS PE/COFF315.1 Windows的二進制文件格式 PE/COFF315.2 PE 的前身 COFF325.3鏈接指示信息 325.4調(diào)試信息325.5大家都有符號表325.6 WINDOWS 下的 ELFPE32第6章可執(zhí)行文件的裝載與進程 336.1進程的虛擬地址空間 336.2裝載的方式 33覆蓋裝入33頁映射346.3從操作系統(tǒng)的角度看可執(zhí)行文件的裝載 34進程的建立 346.4 進程虛存

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

5、初始化信息 467.6動態(tài)鏈接的步驟和實現(xiàn) 46動態(tài)鏈接器自舉46裝載共享對象 47重定位和初始化 477.6.4 Linux動態(tài)鏈接器的實現(xiàn) 477.7顯示運行時鏈接 48打開動態(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符號版本50基于符號的版本機制 508.2.3 Linux中的符號版本 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 下的動態(tài)鏈接 549.1 dll 介紹54基地址和 RVA549.1.3 dll共享數(shù)據(jù)段549.1.4 dll的簡單例子54使用模塊定義文件 559.1.8 DLL顯示運行時鏈接 559.2符號導出導入表55導出表559.2.2 EXP文件56導入表56導入函數(shù)的調(diào)用 569.3 DLL優(yōu)化57重定基地址 579.3.2 序號58導入函數(shù)綁定 589.4 C+與動態(tài)鏈接589.5 DLL HELL59第4部分庫與運行庫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進程堆管理 6310.3.3 Windows 進程堆管理64堆分配算法64第11章運行庫6511.1入口函數(shù)和程序初始化 65程序從 main開始執(zhí)行嗎 65入口函數(shù)是如何實現(xiàn)的 65運行庫與I/O 6611.1.4 MSVC CRT勺入口函數(shù)初始化 6611.2 C/C+運行庫 6711.2.1 C語言運行庫 6711.2.2 C語言標準庫 6711.2.3 glibc 和 MSVC CRT6711.3 運行庫與多線程 6811.3.1 CRT的多線程困擾 6811.3.2 CR

8、T 改進 68線程局部存儲實現(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 的實現(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)用實現(xiàn) 7312.2.3 Linux的新型系統(tǒng)調(diào)用機制 7312.3

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

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

11、rVMeo PlayerWord ProcftssorEmail ClientImage Viewer從下圖可以看出計算機的結(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)用。再往上是運行庫,它提供各種系統(tǒng) API。再往上就是各種系統(tǒng)軟件了。這種設(shè)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

28、2.1被隱藏了的過程以前學的程序的執(zhí)行過程是編輯、編譯、鏈接、執(zhí)行。今天這本書把這個過 程更加細化了,它以C語言中的helloworld程序為例進行說明,講的大概是從編 譯到鏈接的過程。也是包括4步:1、預處理;2、編譯;3、匯編;4、鏈接。從這個順序可以 看出在C語言中預處理是在編譯之前。預編譯預編譯是個獨立的過程,不同于源文件的.cpp格式和頭文件的.h格式,預編 譯得到的文件后綴是.i或者.ii。預編譯的主要動作就是處理代碼中以#開頭的指令,具體可見P64這些步驟。 因為宏已經(jīng)展開所以.i文件不包含任何宏定義??梢愿鶕?jù).i文件查看宏定義和文 件包含是否正確。預編譯需要預編譯器。編譯編譯的

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

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

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

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

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

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

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

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

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

38、煩。其他段表3-2列出了其他段及意義。此外,這個段還可以自定義。3.4 ELF文件結(jié)構(gòu)描述圖3-4展示了 ELF的層次結(jié)構(gòu)。最重要的兩個部分就是ELF文件頭和段表。ELF文件頭描述整個文件的基 本屬性,段表描述各段的信息。文件頭清單3-2清楚地描述了 ELF文件頭的信息,P95黑體部分列舉了 ELF文件 頭包含的信息。ELF文件兼容各平臺,它的文件結(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文件頭的第一個字段是Magic,包含16bytes,對應

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

溫馨提示

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

評論

0/150

提交評論