當前位置:首頁 » 編程軟體 » 實例化編譯器和鏈接器

實例化編譯器和鏈接器

發布時間: 2022-09-21 07:50:30

『壹』 誰知道鏈接器做什麼

前幾天,在組內分享了關於鏈接器的一些東西,在這里總結一下。討論的背景主要是基於C/C++,Linux平台相關。鏈接器相關的一些基本問題 學習或者了解鏈接器,有一些基本的問題需要關心:鏈接器做些什麼;鏈接器和體系結構;程序是怎樣生成的。下面做簡要介紹。鏈接器做些什麼 鏈接器之所以存在或者產生,基本上是由於程序開發的模塊化。這里講的模塊,主要是編譯概念上的模塊,通常他們按照功能劃分,比如一個.c或者.cpp文件就是一個編譯單元,就是一個模塊,編譯後就產生一個.o目標文件。為了最終生成一個可執行文件、靜態庫或者動態庫,就需要把各個編譯單元按照特定的約定組合到一起。這里特定的約定指的就是「目標文件格式」,它定義了目標文件、庫文件和可執行文件的格式,這里組合這一過程就叫做鏈接。
一個編譯模塊中,通常是函數的定義和全局數據的定義,數據類型的定義通常在頭文件中,編譯時會被包含在編譯模塊中。函數和數據由符號來標識,一般符號有全局和靜態之分,全局符號可以被其他模塊引用,而靜態符號只能在本模塊中引用。編譯各個模塊時,編譯器會解析該模塊。重要的一項工作就是建立符號表,符號表中包含了本模塊有哪些符號可以被其他模塊引用(導出符號),還包括本模塊引用(導入符號,即未定義符號)、但在其他模塊中定義的符號。每一個符號都關聯一個地址,這個地址指明了該符號在本模塊中的偏移地址(通常是一個從0開始的地址)。
鏈接器在鏈接過程中,會掃描各個模塊的符號表,得到一個「全局符號表」,鏈接器由此決定一個符號在哪裡被定義,在哪裡被引用。並且,將符號引用處替換為定義處的地址,這一過程就叫做符號解析。
鏈接器的一項終極目標就是生成可執行文件。通常,可執行文件和普通目標文件的重要區別就是地址空間的使用。主流操作系統中,可執行文件都是基於虛擬地址空間的,即每個可執行文件都有相同且獨立的地址空間,並且文件中各個段(代碼段,數據段,以及進程空間中的堆棧段)都有相似的布局。而普通目標文件卻使用從零開始的地址空間,這樣一來,模塊M中的符號m就可能和模塊N中的符號n擁有「相同」的地址。在鏈接器鏈接各個模塊時,會從各個模塊中「提取」類型相同的段進行合並,並將合並後的段寫入可執行文件中。這一過程被稱為存儲空間的分配。值得一提的是,棧、堆以及未初始化的數據這些「運行時」需要的空間不會在可執行文件中占據磁碟空間,但它們佔用相應的地址空間。
由於存在上述「合並」過程,前面提到的符號解析就涉及到另外一個過程:重定位。由於各個模塊中的函數/數據地址會被重新排放,那麼對這些符號的引用也必須被相應地調整。這一調整過程被稱作重定位。
符號解析,存儲空間分配,還有重定位,這三個過程是一個有機的整體,是「同時」進行的,且這三個過程也是模塊化所帶來的必須要解決的問題。鏈接器和體系結構 在我們編寫普通應用程序時,不需要過多的關系體系結構的問題,但對於鏈接器學習者/編寫者來說,相當大的工作都必須圍繞體系結構展開,比如目標平台的ABI,內存地址,指令格式,定址方式等等,下面做大致介紹。
另外,多位元組數據的位元組序也是鏈接器需要考慮的。位元組序是關於如何使用線性的連續位元組來表示多位元組數據的問題,有Little-endian和Big-endian兩種位元組序。小端序是指將多位元組數據的低權重位元組放在內存的低地址處,大端序則正好相反。直觀講,從內存的低地址向高地址方向看,先看到多位元組數據的低權重位元組的就是小端序,否則就是大端序。至於為什麼會存在兩種位元組序,兩者有何優劣,我覺得這只是個「個人喜好」問題,就好象剝雞蛋先磕破哪一頭,蹲廁所時臉里還是臉朝外一樣,事實上,直到前些天,我才親眼看到,真的是有「茅房拉屎臉朝內」的人的。
指令格式和定址方式也會影響鏈接器的工作,因為在符號重定位的時候,鏈接器需要修改指令中操作數部分,所以需要知道每種指令的指令格式及定址方式,以便對指令做出適當的調整。程序是怎樣生成的 事情漸漸明了了,編譯器前端對語言進行詞法、語法分析,建立語法樹,編譯器後端在語法樹的基礎上針對特定的平台生成指令,並按照特定的格式輸出到磁碟中的目標文件。鏈接器按照前面所說的過程生成最終的可執行文件或程序庫。(這里的程序庫特指動態庫,因為靜態庫只是目標文件的簡單集合,理論上不需要鏈接器的參與)本文只針對鏈接器進行簡單的討論,關於編譯器的功能和相關原理,有大量的資料可以參考。目標文件格式 目標文件格式是指令、數據在磁碟中存儲形式的一種約定。它描述了指令、數據的存儲格式和布局,並且針對不同類型的文件(普通目標文件,可執行文件,動態庫)有不同描述側重點。另外它還描述了一些供外部程序使用的元信息,例如普通目標文件中的符號表的內容和組織形式,待重定位符號的信息;可執行文件中各個段的信息,程序的入口點(程序從何處開始執行),哪些符號需要在運行時解析,這些符號包含在哪些動態庫中,以及解析這些庫需要哪種動態鏈接器等等;動態庫為了實現同時被多個進程鏈接需要什麼樣的組織形式,本身又引用了其他動態庫的哪些符號等等。通常,不同的平台都會有自己的目標文件的格式標准:COM:DOS最初採用的一種非常簡單的格式,除了指令、數據之外,基本不包含其他信息。PE:Windows當前採用的目標格式,繼承自COFF,是一種主流的現代目標格式,相比COM有更強大的功能支持。a.out:最初UNIX平台採用的目標格式,簡單且功能強大,但對於C++這樣的高級語言支持不足。ELF:當前Linux/Unix平台採用的主流格式,繼承自a.out,且對高級語言支持很友好。 除了ABI,目標格式的不同,也是在一種操作系統下編譯的程序無法在另一種操作系統中執行的原因。程序庫 終於到庫了,研究庫很有趣,也相當實用。概念上,庫可以分為靜態庫,動態庫,且這兩種庫都可以實現為「共享庫」,但在實現上,靜態共享庫由於需要考慮態度的問題、實現太過復雜且得不償失,現實中很少有這種類型的庫。所以,應用中只存在兩種庫:靜態庫和動態共享庫,下面分別做簡要介紹,關於在Linux中如何創建這兩種庫,可以參考我之前寫的一篇博客,或者其他更詳盡更優秀的資料。靜態庫 在功能特性上,靜態庫是指這樣一種庫,在鏈接時,其中被引用的代碼、數據被「復制」到引用該庫的程序中。在格式上,靜態庫十分簡單,他是普通目標文件的集合,是一種簡單的拼接。事實上,Linux平台下靜態庫.a文件使用獨立的歸檔工具ar建立,為了使鏈接器能夠有效地查找庫中包含的各個目標文件以及符號,經常還需要一個叫做ranlib的工具在.a文件中建立索引。
在鏈接時,鏈接器想普通目標文件一樣使用靜態庫,僅僅多了在庫中查找符號及對應目標文件的過程。動態鏈接庫 動態鏈接庫和靜態庫差異較大,Linux平台,它由ELF格式直接支持。但由於共享庫的特殊性,它需要一些特殊特性的支持:
PIC(Position Independent Code),位置無關代碼。動態共享庫需要在運行時動態地載入到內存,為了在各個進程中調用共享庫中的代碼和數據,就需要將該庫映射到不同進程的進程空間。由於各個進程的地址空間使用情況不盡相同,很難將共享庫映射到各進程相同的位置。這樣一來,就對共享庫的代碼提出了挑戰,它需要能夠在不同的地址區間上都正確的執行。位置無關代碼就是因此而提出的。
位置無關代碼的基本思想是這樣的:將共享中對絕對地址的引用轉化為相對地址的形式。對於函數調用,實現起來很簡單,因為代碼是只讀的,指令間的相對地址也是固定的,只需要將函數調用轉化會相對地址即可。但對於數據的引用就復雜很多了,由於各個進程都需要訪問共享庫中的數據,而這些進程通常是毫無關聯的,一個進程對共享庫數據的修改不應該被其他進程看到。一種好的方法就是讓各個進程都擁有自己的一份數據拷貝。但這又引出一個問題,共享庫是被動態映射的,數據空間也只能在映射時才需要分配,那麼在共享庫代碼中如何引用這些數據,以達到不同進程使用相同的代碼訪問不同的數據呢?
於是另外一種結構被引入了,即GOT(Global Offset Table),全局偏移量表。它的基本思想也是相對地址引用。在共享庫的數據段加入GOT,GOT的表項保存各個數據的地址。由於共享庫中指令和數據段的相對地址在鏈接後是固定的,這樣在指令中就可以使用相對地址來找到GOT的起始地址,然後根據各個數據在GOT的偏移量來找到其對應的地址。而該地址是在共享庫被映射到進程空間時,由動態鏈接器在相應的進程空間中分配並設置的。
接下來的問題就是,進程中如何調用共享庫中的代碼呢?ELF使用一種延遲載入的方法,即當進程調用共享庫中一個函數時,才解析該函數的地址,且只有第一次才解析,第一次解析之後的調用就不會被再次解析,而是將之前解析到的地址保存下來。這里又引入一種機制,叫做PLT(Procere Linkage Table),它和GOT一起(使用共享庫的進程也有一個GOT)引入了一個函數調用的間接層。
類似共享庫的GOT,進程的GOT表項保存了本進程引用的共享庫中的函數地址,但在第一次對該函數的調用之前,該表項保存的並不是函數的地址,而是指向PLT中一個指令的地址。為了方便說明問題,假設進程中main函數引用了libmath.so中的函數add,那麼PLT大致是這個樣子的,1 2 3 4 5 6 7 8 9 10 11 12 13 .plt0:0x080483d0: pushl 0x8049ff80x080483d6:jmp*0x8049ffc...add@plt:0x080483e0 <+0>:jmp*0x804a0000x080483e6 <+6>:push $0x00x080483eb <+11>:jmp0x80483d0... main:...0x080484ec <+24>:call0x80483e0 <add@plt> # 對add函數的調用 ... 第十二行對add函數的調用跳轉到第五行,這是一條jmp指令,0x804a000是它的地址操作數,該地址即是add在GOT中的地址項,最初該地址處保存的地址是0x080483e6,即jmp指令的下一條push指令。於是最初jmp指令執行後沒有任何效果,直接執行下一條指令。push指令將add在重定位項的索引入棧,通過該重定位項可以得到add符號本身(即字元串add)。然後又是一條jmp指令它跳轉到第二行,又是一個push指令,接下來又是jmp指令,這個jmp指令也使用了GOT中的一個表項,該表項存儲的是動態鏈接器(ld.so)的載入/解析函數。在解析函數中,查找add符號在共享庫中的地址,將該地址填入add對應的GOT表項,然後跳轉至add函數開始執行。到下一次調用add函數,第五行的jmp指令就直接跳轉到add函數了。
動態鏈接基本就是這個過程了。在這個過程中有許多有意思的指令,將棧玩弄於鼓掌,像變魔術一樣,有興趣的話可以使用gdb等相關工具調試一下。一些工具 玩弄二進制,很多實用工具是離不了的,最重要的就是GNU Binutils二進制工具鏈了。包括查看ELF文件信息的readelf,對目標文件、可執行文件、共享庫、core內存轉儲文件等反匯編的objmp,重量級的調試器gdb,查看共享庫使用情況的ldd等等。一些參考資料 了解鏈接器工作原理,現成的資料並不多,《Linkers and Loaders》算是經典了,中文版也可以買得到,翻譯得還算中規中矩。另外,《程序員的自我修養:鏈接、裝載與庫》寫的很淺顯,不錯的一本書。
為了更好的理解鏈接器,必須對ELF的細節有所了解,Executable and Linkable Format這份文檔可以參考。
研究二進制,匯編知識是必須的,如果是Linux平台,了解些GNU 匯編(AT&T匯編)是再好不過了,不過講解GNU匯編的資料更是少上加少,布魯姆的《匯編語言程序設計》雖然內容不多,但對非專業匯編程序員也足夠用了,這本書是有英文電子版的。
有了相關工具和入門資料,剩下的就是折騰了。

『貳』 C++模板:這個程序為什麼通不過編譯(C++11)

DoIt 是個模板方法. 要告訴編譯器當成 模板方法對待.

B<T>::templateDoIt<Opr>();

改成這樣就可以了...

『叄』 編譯器跟編輯器有什麼區別。還有什麼是鏈接器

鏈接器(Linker)是一個程序,將一個或多個由編譯器或匯編器生成的目標文件外加庫鏈接為一個可執行文件。

是軟體程序,一般是指用來修改電腦檔案的編寫軟體,但也有人稱 PE2、HE4(漢書)……等文書軟體為編輯器。常見的編輯器有文本編輯器、網頁編輯器、源程序編輯器、圖像編輯器,聲音編輯器,視頻編輯器等。

簡單講,編譯器就是將「一種語言(通常為高級語言)」翻譯為「另一種語言(通常為低級語言)」的程序。一個現代編譯器的主要工作流程:源代碼 (source code) → 預處理器 (preprocessor) → 編譯器 (compiler) → 目標代碼 (object code) → 鏈接器 (Linker) → 可執行程序 (executables)

『肆』 C++模板隱式實例化是編譯器進行實例化還是鏈接器進行實例化

據我所知一般是編譯器生成實例化代碼,鏈接器刪除重復代碼

『伍』 C++模板類實例化,編譯器報錯

第一種方法意味著在使用模板的轉換文件中不但要包含模板聲明文件,還要包含模板定義文件。在上例中,就是第一個示例,在array.h中用行內函數定義了所有的成員函數。或者在main.cpp文件中也包含進array.cpp文件。這樣編譯器就能看到模板的聲明和定義,並由此生成array<int, 50>實例。這樣做的缺點是編譯文件會變得很大,顯然要降低編譯和鏈接速度。
第二種方法,通過顯式的模板實例化得到類型。最好將所有的顯式實例化過程安放在另外的文件中。在本例中,可以創建一個新文件templateinstantiations.cpp:
// templateinstantiations.cpp
#include "array.cpp"
template class array <int, 50>; // 顯式實例化

『陸』 編譯器有什麼用

簡單講,編譯器就是將「一種語言(通常為高級語言)」翻譯為「另一種語言(通常為低級語言)」的程序。一個現代編譯器的主要工作流程:源代碼 (source code) → 預處理器 (preprocessor) → 編譯器 (compiler) → 目標代碼 (object code) → 鏈接器(Linker) → 可執行程序 (executables)
高級計算機語言便於人編寫,閱讀交流,維護。機器語言是計算機能直接解讀、運行的。編譯器將匯編或高級計算機語言源程序(Source program)作為輸入,翻譯成目標語言(Target language)機器代碼的等價程序。源代碼一般為高級語言 (High-level language), 如Pascal、C、C++、Java、漢語編程等或匯編語言,而目標則是機器語言的目標代碼(Object code),有時也稱作機器代碼(Machine code)。
對於C#、VB等高級語言而言,此時編譯器完成的功能是把源碼(SourceCode)編譯成通用中間語言(MSIL/CIL)的位元組碼(ByteCode)。最後運行的時候通過通用語言運行庫的轉換,編程最終可以被CPU直接計算的機器碼(NativeCode)。

『柒』 模板實例化失敗可能得原因

編譯出錯,鏈接出錯,或程序實例化。程序員在使用模板類時最常犯的錯誤是將模板類視為某種數據類型。



所謂類型參量化(parameterized types)這樣的術語導致了這種誤解。模板當然不是數據類型,模板就是模板,恰如其名:編譯器

使用模板,通過更換模板參數來創建數據類型。這個過程就是模板實例化(Instantiation)。



從模板類創建得到的類型稱之為特例(specialization)。模板實例化取決於編譯器能夠找到可用代碼來創建特例(稱之為實例化要素,

point of instantiation)。



要創建特例,編譯器不但要看到模板的聲明,還要看到模板的定義。模板實例化過程是遲鈍的,即只能用函數的定義來實現實例化。



編譯器可以解析模板定義並檢查語法,但不能生成成員函數的代碼。它無法生成代碼,因為要生成代碼,需要知道模板參數,即需要

一個類型,而不是模板本身。

『捌』 簡述一下編譯器和鏈接器的作用

1、編譯器:
編譯器對源文件進行編譯,就是把源文件中的文本形式存在的源代碼翻譯成機器語言形式的目標文件的過程,在這個過程中,編譯器會進行一系列的語法檢查。如果編譯通過,就會把對應的CPP轉換成OBJ文件。
2、鏈接器:
當鏈接器進行鏈接的時候,首先決定各個目標文件在最終可執行文件里的位置。然後訪問所有目標文件的地址重定義表,對其中記錄的地址進行重定向(加上一個偏移量,即該編譯單元在可執行文件上的起始地址)。
然後遍歷所有目標文件的未解決符號表,並且在所有的導出符號表裡查找匹配的符號,並在未解決符號表中所記錄的位置上填寫實現地址。最後把所有的目標文件的內容寫在各自的位置上,再作一些另的工作,就生成一個可執行文件。

『玖』 C++ primer 模板實例化

舉個例子來說:
template <class _Tx>
_Tx Max_T(_Tx a, _Tx b) { return a >b ? a, b; }
這是個模板的聲明,用途是返回兩個對象中較大的那一個,使用模板形參_Tx,,這里可以 是任何基本類型比如int, char, double等
請看下面代碼:
void main(void)
{
int a = 1, b = 2;
int max = Max_T(a, b);
printf("%d\n", max);
}
int max = Max_T(a, b);這一句表示模板的使用,意思是在代碼中使用了模板
編譯器有個預處理器,負責將模板實例成代碼(當然它還干其它的工作),再交給編譯器去編譯。
簡單來說是這個樣子:當編譯器發現存在這樣的模板調用代碼時Max_T(a, b);它會先用int 來實例化模板的_Tx形參,然後生成一個函數:@xxxMax_Txxx(int a, int b)這個名字是裝飾名(這里不作解釋,如果函數體內部還有對形參_Tx的引用,也全部用int替換掉),生成函數之後然後用@xxxMax_Txxx(a, b)來替換模板調用Max_T(a, b);的這一行代碼,這樣就生成了實際的代碼,這一步叫做模板的實例化,這一步完成之後,編譯器的編譯單元就可以像正常代碼一樣去編譯這個代碼了,最後生成目標代碼,然後交給鏈接器去生成可執行文件(扯遠了),整個過程就是這樣,現在明白了否

『拾』 c++;明確的實例化有什麼作用

明確的實例化就是讓編譯器按這個參數編譯一份可鏈接的類出來。 (否則模板類的原則是不使用就不編譯)

明確定義就是所謂「特化」吧, 大致相當於在指定特殊參數下對類「重載」

如果你寫一個跟本身毫無差別的特化, 某種角度說,跟明確實例化的結果很接近, 但是在編譯器自動匹配調用時的模板參數時,這兩者可能又有些差別。 這個匹配過程比較復雜,就很難說清楚了。

「調用模板就要實例化,既然這個不調用又有什麼意義啊? 」 在分開編譯的時候這個就會有意義。 因為分開的話,編譯模板類的cpp的時候編譯器還不知道你要怎麼調用呢

熱點內容
scratch少兒編程課程 發布:2025-04-16 17:11:44 瀏覽:631
榮耀x10從哪裡設置密碼 發布:2025-04-16 17:11:43 瀏覽:360
java從入門到精通視頻 發布:2025-04-16 17:11:43 瀏覽:76
php微信介面教程 發布:2025-04-16 17:07:30 瀏覽:301
android實現陰影 發布:2025-04-16 16:50:08 瀏覽:789
粉筆直播課緩存 發布:2025-04-16 16:31:21 瀏覽:339
機頂盒都有什麼配置 發布:2025-04-16 16:24:37 瀏覽:204
編寫手游反編譯都需要學習什麼 發布:2025-04-16 16:19:36 瀏覽:804
proteus編譯文件位置 發布:2025-04-16 16:18:44 瀏覽:358
土壓縮的本質 發布:2025-04-16 16:13:21 瀏覽:584