日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無(wú)先后,達(dá)者為師

網(wǎng)站首頁(yè) 編程語(yǔ)言 正文

C/C++?-?從代碼到可執(zhí)行程序的過(guò)程詳解_C 語(yǔ)言

作者:give?it?a?try~ ? 更新時(shí)間: 2023-03-11 編程語(yǔ)言

(1)預(yù)編譯

主要處理源代碼文件中的以“#”開(kāi)頭的預(yù)編譯指令。處理規(guī)則見(jiàn)下:

刪除所有的#define,展開(kāi)所有的宏定義。處理所有的條件預(yù)編譯指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。處理“#include”預(yù)編譯指令,將文件內(nèi)容替換到它的位置,這個(gè)過(guò)程是遞歸進(jìn)行的,文件中包含其
他文件。刪除所有的注釋,“//”和“/**/”。保留所有的#pragma 編譯器指令,編譯器需要用到他們,如:#pragma once 是為了防止有文件
被重復(fù)引用。添加行號(hào)和文件標(biāo)識(shí),便于編譯時(shí)編譯器產(chǎn)生調(diào)試用的行號(hào)信息,和編譯時(shí)產(chǎn)生編譯錯(cuò)誤或警告是
能夠顯示行號(hào)。

(2)編譯

把預(yù)編譯之后生成的xxx.i或xxx.ii文件,進(jìn)行一系列詞法分析、語(yǔ)法分析、語(yǔ)義分析及優(yōu)化后,生成相應(yīng)
的匯編代碼文件。

詞法分析:利用類似于“有限狀態(tài)機(jī)”的算法,將源代碼程序輸入到掃描機(jī)中,將其中的字符序列分
割成一系列的記號(hào)。語(yǔ)法分析:語(yǔ)法分析器對(duì)由掃描器產(chǎn)生的記號(hào),進(jìn)行語(yǔ)法分析,產(chǎn)生語(yǔ)法樹(shù)。由語(yǔ)法分析器輸出的
語(yǔ)法樹(shù)是一種以表達(dá)式為節(jié)點(diǎn)的樹(shù)。語(yǔ)義分析:語(yǔ)法分析器只是完成了對(duì)表達(dá)式語(yǔ)法層面的分析,語(yǔ)義分析器則對(duì)表達(dá)式是否有意義進(jìn)
行判斷,其分析的語(yǔ)義是靜態(tài)語(yǔ)義——在編譯期能分期的語(yǔ)義,相對(duì)應(yīng)的動(dòng)態(tài)語(yǔ)義是在運(yùn)行期才能
確定的語(yǔ)義。優(yōu)化:源代碼級(jí)別的一個(gè)優(yōu)化過(guò)程。目標(biāo)代碼生成:由代碼生成器將中間代碼轉(zhuǎn)換成目標(biāo)機(jī)器代碼,生成一系列的代碼序列——匯編語(yǔ)
言表示。目標(biāo)代碼優(yōu)化:目標(biāo)代碼優(yōu)化器對(duì)上述的目標(biāo)機(jī)器代碼進(jìn)行優(yōu)化:尋找合適的尋址方式、使用位移
來(lái)替代乘法運(yùn)算、刪除多余的指令等。

(3)匯編

將匯編代碼轉(zhuǎn)變成機(jī)器可以執(zhí)行的指令(機(jī)器碼文件)。 匯編器的匯編過(guò)程相對(duì)于編譯器來(lái)說(shuō)更簡(jiǎn)單,沒(méi)
有復(fù)雜的語(yǔ)法,也沒(méi)有語(yǔ)義,更不需要做指令優(yōu)化,只是根據(jù)匯編指令和機(jī)器指令的對(duì)照表一一翻譯過(guò)
來(lái),匯編過(guò)程有匯編器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)庫(kù)的情況下,在編譯鏈接可執(zhí)行文件時(shí),鏈接器從庫(kù)
中復(fù)制這些函數(shù)和數(shù)據(jù)并把它們和應(yīng)用程序的其它模塊組合起來(lái)創(chuàng)建最終的可執(zhí)行文件。

以下面這個(gè)圖來(lái)簡(jiǎn)單說(shuō)明一下從靜態(tài)鏈接到可執(zhí)行文件的過(guò)程,根據(jù)在源文件中包含的頭文件和程序中使用到的庫(kù)函數(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è)小問(wèn)題,就是從上面的圖中可以看到靜態(tài)運(yùn)行庫(kù)里面的一個(gè)目標(biāo)文件只包含一個(gè)函數(shù),如libc.a里面的printf.o只有printf()函數(shù),strlen.o里面只有strlen()函數(shù)。

我們知道,鏈接器在鏈接靜態(tài)鏈接庫(kù)的時(shí)候是以目標(biāo)文件為單位的。比如我們引用了靜態(tài)庫(kù)中的printf()函數(shù),那么鏈接器就會(huì)把庫(kù)中包含printf()函數(shù)的那個(gè)目標(biāo)文件鏈接進(jìn)來(lái),如果很多函數(shù)都放在一個(gè)目標(biāo)文件中,很可能很多沒(méi)用的函數(shù)都被一起鏈接進(jìn)了輸出結(jié)果中。由于運(yùn)行庫(kù)有成百上千個(gè)函數(shù),數(shù)量非常龐大,每個(gè)函數(shù)獨(dú)立地放在一個(gè)目標(biāo)文件中可以盡量減少空間的浪費(fèi),那些沒(méi)有被用到的目標(biāo)文件就不要鏈接到最終的輸出文件中。

缺點(diǎn):
空間浪費(fèi):因?yàn)槊總€(gè)可執(zhí)行程序中對(duì)所有需要的目標(biāo)文件都要有一份副本,所以如果多個(gè)程序?qū)ν粋€(gè)
目標(biāo)文件都有依賴,會(huì)出現(xiàn)同一個(gè)目標(biāo)文件都在內(nèi)存存在多個(gè)副本;
更新困難:每當(dāng)庫(kù)函數(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è)相對(duì)獨(dú)立部分,在程序運(yùn)行時(shí)才將它們鏈接在一起形成一個(gè)完整的程序,而不是像靜態(tài)鏈接一樣把所有程序模塊都鏈接成一個(gè)單獨(dú)的可執(zhí)行文件。

假設(shè)現(xiàn)在有兩個(gè)程序program1.o和program2.o,這兩者共用同一個(gè)庫(kù)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è)鏈接過(guò)程和靜態(tài)鏈接類似)形成可執(zhí)行程序。

優(yōu)點(diǎn):
共享庫(kù):就是即使需要每個(gè)程序都依賴同一個(gè)庫(kù),但是該庫(kù)不會(huì)像靜態(tài)鏈接那樣在內(nèi)存中存在多分,副
本,而是這多個(gè)程序在執(zhí)行時(shí)共享同一份副本;更新方便:更新時(shí)只需要替換原來(lái)的目標(biāo)文件,而無(wú)需將所有的程序再重新鏈接一遍。當(dāng)程序下一次運(yùn)行時(shí),新版本的目標(biāo)文件會(huì)被自動(dòng)加載到內(nèi)存并且鏈接起來(lái),程序就完成了升級(jí)的目標(biāo)。
性能損耗:因?yàn)榘焰溄油七t到了程序運(yùn)行時(shí),所以每次執(zhí)行程序都需要進(jìn)行鏈接,所以性能會(huì)有一定損失。

生成可執(zhí)行文件

什么情況會(huì)編譯成功但鏈接失敗

(1)說(shuō)明并使用了類型、函數(shù)、變量,但沒(méi)給出相應(yīng)類型、函數(shù)或變量的定義。關(guān)于說(shuō)明和定義的區(qū)別見(jiàn)上述教程,說(shuō)明可以通過(guò)#include頭文件進(jìn)行,也可以直接進(jìn)行說(shuō)明如int f( )或extern int f( )。若同時(shí)給出函數(shù)f的函數(shù)體則稱為定義。對(duì)于變量若有初始值就算定義,例如"extern int x=3; "便是定義,這種語(yǔ)法有其獨(dú)特應(yīng)用背景。

(2)對(duì)標(biāo)準(zhǔn)庫(kù)的連接失敗,例如調(diào)用了sin(double x),卻找不到數(shù)學(xué)運(yùn)算標(biāo)準(zhǔn)庫(kù)進(jìn)行連接了,可能是安裝后因各種可能原因被刪除了。

(3)全局變量和靜態(tài)變量存放在數(shù)據(jù)段,而固定大小的數(shù)據(jù)段不夠用了,例如定義太多的全局變量和靜態(tài)變量,甚至巨型數(shù)組全局或靜態(tài)變量。

(4)數(shù)據(jù)段被偷用后無(wú)法容納較多的全局變量和靜態(tài)變量,這種錯(cuò)誤最難發(fā)現(xiàn)且最難改正。主要是一些常量在偷用數(shù)據(jù)段,例如字符串常量"abc"等等,其它諸多情形參見(jiàn)上述教程。另外虛函數(shù)入口地址表等也會(huì)偷用。

原文鏈接:https://blog.csdn.net/moasad/article/details/128666033

欄目分類
最近更新