C/C++?-?從代碼到可執(zhí)行程序的過程詳解
(1)預(yù)編譯
主要處理源代碼文件中的以“#”開頭的預(yù)編譯指令。處理規(guī)則見下:
刪除所有的#define,展開所有的宏定義。處理所有的條件預(yù)編譯指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。處理“#include”預(yù)編譯指令,將文件內(nèi)容替換到它的位置,這個(gè)過程是遞歸進(jìn)行的,文件中包含其
他文件。刪除所有的注釋,“//”和“/**/”。保留所有的#pragma 編譯器指令,編譯器需要用到他們,如:#pragma once 是為了防止有文件
被重復(fù)引用。添加行號和文件標(biāo)識,便于編譯時(shí)編譯器產(chǎn)生調(diào)試用的行號信息,和編譯時(shí)產(chǎn)生編譯錯(cuò)誤或警告是
能夠顯示行號。
(2)編譯
把預(yù)編譯之后生成的xxx.i或xxx.ii文件,進(jìn)行一系列詞法分析、語法分析、語義分析及優(yōu)化后,生成相應(yīng)
的匯編代碼文件。
詞法分析:利用類似于“有限狀態(tài)機(jī)”的算法,將源代碼程序輸入到掃描機(jī)中,將其中的字符序列分
割成一系列的記號。語法分析:語法分析器對由掃描器產(chǎn)生的記號,進(jìn)行語法分析,產(chǎn)生語法樹。由語法分析器輸出的
語法樹是一種以表達(dá)式為節(jié)點(diǎn)的樹。語義分析:語法分析器只是完成了對表達(dá)式語法層面的分析,語義分析器則對表達(dá)式是否有意義進(jìn)
行判斷,其分析的語義是靜態(tài)語義——在編譯期能分期的語義,相對應(yīng)的動(dòng)態(tài)語義是在運(yùn)行期才能
確定的語義。優(yōu)化:源代碼級別的一個(gè)優(yōu)化過程。目標(biāo)代碼生成:由代碼生成器將中間代碼轉(zhuǎn)換成目標(biāo)機(jī)器代碼,生成一系列的代碼序列——匯編語
言表示。目標(biāo)代碼優(yōu)化:目標(biāo)代碼優(yōu)化器對上述的目標(biāo)機(jī)器代碼進(jìn)行優(yōu)化:尋找合適的尋址方式、使用位移
來替代乘法運(yùn)算、刪除多余的指令等。
(3)匯編
將匯編代碼轉(zhuǎn)變成機(jī)器可以執(zhí)行的指令(機(jī)器碼文件)。 匯編器的匯編過程相對于編譯器來說更簡單,沒
有復(fù)雜的語法,也沒有語義,更不需要做指令優(yōu)化,只是根據(jù)匯編指令和機(jī)器指令的對照表一一翻譯過
來,匯編過程有匯編器as完成。經(jīng)匯編之后,產(chǎn)生目標(biāo)文件(與可執(zhí)行文件格式幾乎一樣)xxx.o(Windows 下)、xxx.obj(Linux下)。
(4)鏈接
將不同的源文件產(chǎn)生的目標(biāo)文件進(jìn)行鏈接,從而形成一個(gè)可以執(zhí)行的程序。鏈接分為靜態(tài)鏈接和動(dòng)態(tài)鏈
接:
靜態(tài)鏈接
函數(shù)和數(shù)據(jù)被編譯進(jìn)一個(gè)二進(jìn)制文件。在使用靜態(tài)庫的情況下,在編譯鏈接可執(zhí)行文件時(shí),鏈接器從庫
中復(fù)制這些函數(shù)和數(shù)據(jù)并把它們和應(yīng)用程序的其它模塊組合起來創(chuàng)建最終的可執(zhí)行文件。
以下面這個(gè)圖來簡單說明一下從靜態(tài)鏈接到可執(zhí)行文件的過程,根據(jù)在源文件中包含的頭文件和程序中使用到的庫函數(shù),如stdio.h中定義的printf()函數(shù),在libc.a中找到目標(biāo)文件printf.o(這里暫且不考慮printf()函數(shù)的依賴關(guān)系),然后將這個(gè)目標(biāo)文件和我們hello.o這個(gè)文件進(jìn)行鏈接形成我們的可執(zhí)行文件。
這里有一個(gè)小問題,就是從上面的圖中可以看到靜態(tài)運(yùn)行庫里面的一個(gè)目標(biāo)文件只包含一個(gè)函數(shù),如libc.a里面的printf.o只有printf()函數(shù),strlen.o里面只有strlen()函數(shù)。
我們知道,鏈接器在鏈接靜態(tài)鏈接庫的時(shí)候是以目標(biāo)文件為單位的。比如我們引用了靜態(tài)庫中的printf()函數(shù),那么鏈接器就會把庫中包含printf()函數(shù)的那個(gè)目標(biāo)文件鏈接進(jìn)來,如果很多函數(shù)都放在一個(gè)目標(biāo)文件中,很可能很多沒用的函數(shù)都被一起鏈接進(jìn)了輸出結(jié)果中。由于運(yùn)行庫有成百上千個(gè)函數(shù),數(shù)量非常龐大,每個(gè)函數(shù)獨(dú)立地放在一個(gè)目標(biāo)文件中可以盡量減少空間的浪費(fèi),那些沒有被用到的目標(biāo)文件就不要鏈接到最終的輸出文件中。
缺點(diǎn):
空間浪費(fèi):因?yàn)槊總€(gè)可執(zhí)行程序中對所有需要的目標(biāo)文件都要有一份副本,所以如果多個(gè)程序?qū)ν粋€(gè)
目標(biāo)文件都有依賴,會出現(xiàn)同一個(gè)目標(biāo)文件都在內(nèi)存存在多個(gè)副本;
更新困難:每當(dāng)庫函數(shù)的代碼修改了,這個(gè)時(shí)候就需要重新進(jìn)行編譯鏈接形成可執(zhí)行程序。
運(yùn)行速度快:但是靜態(tài)鏈接的優(yōu)點(diǎn)就是,在可執(zhí)行程序中已經(jīng)具備了所有執(zhí)行程序所需要的任何東西,
在執(zhí)行的時(shí)候運(yùn)行速度快。
動(dòng)態(tài)鏈接
動(dòng)態(tài)鏈接的基本思想是把程序按照模塊拆分成各個(gè)相對獨(dú)立部分,在程序運(yùn)行時(shí)才將它們鏈接在一起形成一個(gè)完整的程序,而不是像靜態(tài)鏈接一樣把所有程序模塊都鏈接成一個(gè)單獨(dú)的可執(zhí)行文件。
假設(shè)現(xiàn)在有兩個(gè)程序program1.o和program2.o,這兩者共用同一個(gè)庫lib.o,假設(shè)首先運(yùn)行程序program1,系統(tǒng)首先加載program1.o,當(dāng)系統(tǒng)發(fā)現(xiàn)program1.o中用到了lib.o,即program1.o依賴于lib.o,那么系統(tǒng)接著加載lib.o,如果program1.o和lib.o還依賴于其他目標(biāo)文件,則依次全部加載到內(nèi)存中。當(dāng)program2運(yùn)行時(shí),同樣的加載program2.o,然后發(fā)現(xiàn)program2.o依賴于lib.o,但是此時(shí)lib.o已經(jīng)存在于內(nèi)存中,這個(gè)時(shí)候就不再進(jìn)行重新加載,而是將內(nèi)存中已經(jīng)存在的lib.o映射到program2的虛擬地址空間中,從而進(jìn)行鏈接(這個(gè)鏈接過程和靜態(tài)鏈接類似)形成可執(zhí)行程序。
優(yōu)點(diǎn):
共享庫:就是即使需要每個(gè)程序都依賴同一個(gè)庫,但是該庫不會像靜態(tài)鏈接那樣在內(nèi)存中存在多分,副
本,而是這多個(gè)程序在執(zhí)行時(shí)共享同一份副本;更新方便:更新時(shí)只需要替換原來的目標(biāo)文件,而無需將所有的程序再重新鏈接一遍。當(dāng)程序下一次運(yùn)行時(shí),新版本的目標(biāo)文件會被自動(dòng)加載到內(nèi)存并且鏈接起來,程序就完成了升級的目標(biāo)。
性能損耗:因?yàn)榘焰溄油七t到了程序運(yùn)行時(shí),所以每次執(zhí)行程序都需要進(jìn)行鏈接,所以性能會有一定損失。
生成可執(zhí)行文件
什么情況會編譯成功但鏈接失敗
(1)說明并使用了類型、函數(shù)、變量,但沒給出相應(yīng)類型、函數(shù)或變量的定義。關(guān)于說明和定義的區(qū)別見上述教程,說明可以通過#include頭文件進(jìn)行,也可以直接進(jìn)行說明如int f( )或extern int f( )。若同時(shí)給出函數(shù)f的函數(shù)體則稱為定義。對于變量若有初始值就算定義,例如"extern int x=3; "便是定義,這種語法有其獨(dú)特應(yīng)用背景。
(2)對標(biāo)準(zhǔn)庫的連接失敗,例如調(diào)用了sin(double x),卻找不到數(shù)學(xué)運(yùn)算標(biāo)準(zhǔn)庫進(jìn)行連接了,可能是安裝后因各種可能原因被刪除了。
(3)全局變量和靜態(tài)變量存放在數(shù)據(jù)段,而固定大小的數(shù)據(jù)段不夠用了,例如定義太多的全局變量和靜態(tài)變量,甚至巨型數(shù)組全局或靜態(tài)變量。
(4)數(shù)據(jù)段被偷用后無法容納較多的全局變量和靜態(tài)變量,這種錯(cuò)誤最難發(fā)現(xiàn)且最難改正。主要是一些常量在偷用數(shù)據(jù)段,例如字符串常量"abc"等等,其它諸多情形參見上述教程。另外虛函數(shù)入口地址表等也會偷用。
到此這篇關(guān)于C/C++ - 從代碼到可執(zhí)行程序的過程的文章就介紹到這了,更多相關(guān)C++ 從代碼到可執(zhí)行程序的過程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言庫函數(shù)strcpy的使用及模擬實(shí)現(xiàn)
本文主要介紹了C語言庫函數(shù)strcpy的使用及模擬實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-04-04C++ 數(shù)據(jù)結(jié)構(gòu)二叉樹(前序/中序/后序遞歸、非遞歸遍歷)
這篇文章主要介紹了C++ 數(shù)據(jù)結(jié)構(gòu)二叉樹(前序/中序/后序遞歸、非遞歸遍歷)的相關(guān)資料,這里提供實(shí)例代碼來幫助大家理解掌握二叉樹,需要的朋友可以參考下2017-07-07詳解VS2019 dumpbin查看DLL的導(dǎo)出函數(shù)
這篇文章主要介紹了詳解VS2019 dumpbin查看DLL的導(dǎo)出函數(shù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08用C語言來實(shí)現(xiàn)一個(gè)簡單的虛擬機(jī)
這篇文章主要介紹了用C語言來實(shí)現(xiàn)一個(gè)簡單的虛擬機(jī),其中棧數(shù)組的部分非常值得學(xué)習(xí),需要的朋友可以參考下2015-07-07