一個(gè)程序是如何被機(jī)器運(yùn)行起來(lái)的北戏?

由于前段時(shí)間期末考試,所以一直沒(méi)有更新博客漫蛔,最近又來(lái)了搜狐實(shí)習(xí)嗜愈,一直在趕需求,感覺(jué)自己好久沒(méi)有更新博客了莽龟,這幾天趕完了需求蠕嫁,還是抽時(shí)間來(lái)更新一下博客吧。

正文

我們平常寫程序的時(shí)候毯盈,一般都是使用一個(gè)好用的IDE剃毒,然后寫好代碼,run一下程序就運(yùn)行起來(lái)了搂赋,但是不知道大家是不是也思考過(guò)程序到底是怎么運(yùn)行起來(lái)的呢迟赃?

這其實(shí)是一個(gè)很復(fù)雜的過(guò)程,我的了解也是非常的淺顯厂镇,所以只能簡(jiǎn)單介紹一下它的大概步驟纤壁。

一個(gè)典型的程序運(yùn)行步驟如下:

  • 操作系統(tǒng)在創(chuàng)建進(jìn)程后,把控制權(quán)交到程序的入口捺信,這個(gè)入口往往是運(yùn)行庫(kù)中的某個(gè)入口函數(shù)
  • 入口函數(shù)對(duì)運(yùn)行庫(kù)和程序運(yùn)行環(huán)境進(jìn)行初始化酌媒,包括堆欠痴、I/O、線程秒咨、全局變量的構(gòu)造
  • 入口函數(shù)在完成初始化后喇辽,調(diào)用main函數(shù),正式開始執(zhí)行程序主體部分
  • main函數(shù)執(zhí)行完畢后雨席,返回到入口函數(shù)菩咨,入口函數(shù)進(jìn)行清理工作,包括全局變量析構(gòu)陡厘、堆銷毀抽米、關(guān)閉I/O等,然后系統(tǒng)調(diào)用結(jié)束進(jìn)程

Build一個(gè)程序?qū)嶋H上包含四個(gè)步驟:

  • 預(yù)處理(Prepression)
  • 編譯(Compilation)
  • 匯編(Assembly)
  • 鏈接(Linking)

下面以最簡(jiǎn)單的一段C語(yǔ)言程序?yàn)槔谥茫褂肎CC編譯過(guò)程如下:

#include <stdio.h>

int main()
{
    printf("Hello World\n");
    return 0;
}
GCC編譯過(guò)程分解
預(yù)編譯:

源代碼文件和相關(guān)的頭文件被預(yù)編譯器cpp預(yù)編譯成一個(gè).i文件云茸。

相當(dāng)于如下命令:

$gcc -E hello,c -o hello.i或者$cpp hello.c > hello.i

預(yù)編譯過(guò)程主要處理那些源代碼文件中以”#”開始的預(yù)編譯指令。如”#include”谤饭、”#define”等标捺,處理規(guī)則如下:

  • 將所有的"#define"刪除,并且展開所有的宏定義
  • 處理所有條件預(yù)編譯指令揉抵,比如"#if"亡容、"#ifdef"、"#elif"冤今、"#else"萍倡、"#endif"
  • 處理"#include"預(yù)編譯指令,將被包含的文件插入到該預(yù)處理指令位置辟汰。這是一個(gè)遞歸過(guò)程列敲,也就是說(shuō)被包含的文件可能還包含其他文件。
  • 刪除所有的注釋
  • 添加行號(hào)和文件名標(biāo)識(shí)帖汞,以便編譯時(shí)編譯器產(chǎn)生調(diào)試用的行號(hào)信息及用于編譯時(shí)產(chǎn)生編譯錯(cuò)誤或警告時(shí)能夠顯示行號(hào)
  • 保留所有的#pragma編譯器指令戴而,因?yàn)榫幾g器需要使用它們
編譯:

編譯過(guò)程就是把預(yù)處理完的文件進(jìn)行一系列詞法分析、語(yǔ)法分析翩蘸、語(yǔ)義分析及優(yōu)化后生產(chǎn)相應(yīng)的匯編代碼文件所意,這個(gè)過(guò)程往往是我們所說(shuō)的整個(gè)程序構(gòu)建的核心部分,也是復(fù)雜的部分之一催首。

相當(dāng)于如下命令:
$gcc -S hello.i -o hello.s
預(yù)編譯和編譯兩個(gè)步驟也可以合并成一個(gè)步驟:
$gcc -S hello.c -o hello.s
實(shí)際上gcc這個(gè)命令只是后臺(tái)程序的包裝扶踊,它會(huì)根據(jù)不同的參數(shù)要求去調(diào)用預(yù)編譯編譯程序cc1(c++是cc1plus、Objective-C是cclobj)郎任、匯編器as秧耗、鏈接器ld。

以下面這段代碼來(lái)分析一下編譯器所做的事:

array[index] = (index + 4) * (2 + 6)
  • 詞法分析:

詞法分析很簡(jiǎn)單舶治,程序被輸入到掃描器分井,掃描器運(yùn)用一種類似于有限狀態(tài)機(jī)的算法將源代碼的字符序號(hào)分割成一系列的記號(hào)车猬。

  • 語(yǔ)法分析:

語(yǔ)法分析器對(duì)掃描器產(chǎn)生的記號(hào)進(jìn)行語(yǔ)法分析,從而產(chǎn)生語(yǔ)法樹尺锚。上面這段代碼會(huì)生成如下的語(yǔ)法樹:


語(yǔ)法樹
  • 語(yǔ)義分析

語(yǔ)法分析僅僅是完成了對(duì)表達(dá)式的語(yǔ)法層面的分析珠闰,但它并不了解這個(gè)語(yǔ)句是否真的有意義。比如兩個(gè)指針做乘法等瘫辩,語(yǔ)義分析器所能分析的語(yǔ)義是靜態(tài)語(yǔ)義伏嗜,靜態(tài)語(yǔ)義是指在編譯期可以確定的語(yǔ)義。靜態(tài)語(yǔ)義通常包含申明和類型的匹配伐厌,類型的轉(zhuǎn)換承绸。上面的例子經(jīng)過(guò)語(yǔ)義分析后會(huì)變成以下形式:

標(biāo)識(shí)語(yǔ)義后的語(yǔ)法樹

可以看到每個(gè)表達(dá)式都被標(biāo)識(shí)了類型。

  • 中間語(yǔ)言生成

中間語(yǔ)言生成主要就是源碼級(jí)優(yōu)化器對(duì)源代碼進(jìn)行優(yōu)化弧械,如上面的例子中八酒,(2 + 6)這個(gè)表達(dá)式可以被優(yōu)化成8空民,由于直接在語(yǔ)法樹上作優(yōu)化比較困難刃唐,所以源代碼優(yōu)化器往往將整個(gè)語(yǔ)法樹轉(zhuǎn)換成中間代碼。

優(yōu)化后的語(yǔ)法樹
  • 目標(biāo)代碼生成與優(yōu)化

源代碼級(jí)優(yōu)化器產(chǎn)生中間代碼后的過(guò)程都屬于編譯器后端界轩。編譯器后端包括代碼生成器和目標(biāo)代碼優(yōu)化器画饥。代碼生成器將中間代碼轉(zhuǎn)換成目標(biāo)機(jī)器代碼,目標(biāo)代碼優(yōu)化器對(duì)目標(biāo)代碼進(jìn)行優(yōu)化浊猾,比如選擇合適的尋址方式抖甘、使用位移來(lái)代替乘法運(yùn)算、刪除多余的指令等葫慎。

匯編:

匯編是將匯編代碼轉(zhuǎn)變成機(jī)器可以執(zhí)行的指令衔彻,每一個(gè)匯編語(yǔ)句幾乎都對(duì)應(yīng)一條機(jī)器指令。所以匯編器的匯編過(guò)程相對(duì)于編譯器來(lái)講比較簡(jiǎn)單偷办,它沒(méi)有復(fù)雜的語(yǔ)法艰额,也沒(méi)有語(yǔ)義,也不需要做指令優(yōu)化椒涯,只是根據(jù)匯編指令和機(jī)器指令的對(duì)照表一一翻譯就可以了柄沮。

$as hello.s -o hello.o或者$gcc -c hello.s -o hello.o

也可以使用gcc命令從C源代碼文件開始,經(jīng)過(guò)預(yù)編譯废岂、編譯和匯編直接輸出目標(biāo)文件(Object File):

$gcc -c hello.c -o hello.o

鏈接:

由于我們一個(gè)程序通常包含很多個(gè)模塊祖搓,這些模塊之間相互依賴又相互獨(dú)立,所以我們一般寫程序的時(shí)候?qū)Τ绦蜻M(jìn)行了分割湖苞,鏈接就相當(dāng)于把這些分割的模塊拼接在一起拯欧,最終生成一個(gè)可執(zhí)行文件。

鏈接通常是一個(gè)讓人比較費(fèi)解的過(guò)程财骨,鏈接包括靜態(tài)鏈接動(dòng)態(tài)鏈接哈扮。

  • 靜態(tài)鏈接

最基本的靜態(tài)鏈接過(guò)程如圖所示纬纪。每個(gè)模塊的源代碼文件經(jīng)過(guò)編譯器編譯成目標(biāo)文件(一般擴(kuò)展名為.o或者.obj),目標(biāo)文件和庫(kù)一起鏈接形成最終可執(zhí)行的文件滑肉。最常見的庫(kù)就是運(yùn)行時(shí)庫(kù)(Runtime Library)包各,它是支持程序運(yùn)行的基本函數(shù)的集合。鏈接的過(guò)程包括符號(hào)解析靶庙、地址重定位等问畅。


鏈接過(guò)程
  • 動(dòng)態(tài)鏈接

靜態(tài)鏈接使得不同的程序開發(fā)者和部門能夠相對(duì)獨(dú)立的開發(fā)和測(cè)試自己的程序模塊,大大促進(jìn)了程序的開發(fā)效率六荒,但是隨著程序的增大护姆,很多缺點(diǎn)也暴露了出來(lái),比如浪費(fèi)內(nèi)存和磁盤空間掏击、模塊更新困難等卵皂,由此動(dòng)態(tài)鏈接的產(chǎn)生就是為了解決這些問(wèn)題。

為什么說(shuō)靜態(tài)鏈接會(huì)造成內(nèi)存浪費(fèi)砚亭?

舉個(gè)栗子灯变,一個(gè)程序內(nèi)部除了都保留著printf()、scanf()捅膘、strlen()等這樣的公用函數(shù)添祸,還有相當(dāng)數(shù)量的其它庫(kù)函數(shù)以及它們所需要的輔助數(shù)據(jù)結(jié)構(gòu),如果我們操作系統(tǒng)運(yùn)行了很多個(gè)程序寻仗,而這些程序基本都會(huì)使用到C語(yǔ)言的靜態(tài)庫(kù)刃泌,一般這些常用的靜態(tài)庫(kù)至少占1MB以上,如果我們運(yùn)行了100個(gè)這樣的程序署尤,那每個(gè)程序都會(huì)保存一個(gè)副本在內(nèi)存中進(jìn)行靜態(tài)鏈接耙替,那就要浪費(fèi)100MB內(nèi)存空間。

模塊更新的困難曹体?

同樣舉個(gè)栗子俗扇。如果一個(gè)程序Program1所使用的Lib.o是由一個(gè)第三方廠商提供的,當(dāng)該廠商更新了Lib.o的時(shí)候混坞,那個(gè)Program1的廠商就需要拿到最新版的Lib.o狐援,然后將其與Program1鏈接后,將新的Program1整個(gè)發(fā)布給用戶究孕。這樣做的缺點(diǎn)就是一旦程序中有任何模塊更新啥酱,整個(gè)程序就要重新鏈接、發(fā)布給用戶厨诸。這樣一旦程序任何位置的一個(gè)小改動(dòng)镶殷,都會(huì)導(dǎo)致整個(gè)程序重新下載。

動(dòng)態(tài)鏈接就不需要把程序的所有模塊的目標(biāo)文件全都鏈接在一起后再生成可執(zhí)行文件了微酬,它是要等到程序運(yùn)行時(shí)才進(jìn)行鏈接绘趋。也就是說(shuō)颤陶,動(dòng)態(tài)鏈接就是把鏈接的過(guò)程推遲到了程序運(yùn)行時(shí)再進(jìn)行。

還是舉個(gè)栗子吧陷遮。假如Program1和Program2都用到了庫(kù)Lib.o滓走,當(dāng)系統(tǒng)加載Program1程序的時(shí)候,系統(tǒng)就會(huì)加載Lib.o以及Program1依賴的所有目標(biāo)文件帽馋,Program1依賴的所有文件也都加載到了內(nèi)存中后搅方,它就會(huì)進(jìn)行鏈接(和動(dòng)態(tài)鏈接類似)。然后系統(tǒng)就把控制權(quán)交給Program1.o的程序入口绽族,程序就開始運(yùn)行姨涡,=。這個(gè)時(shí)候吧慢,如果再運(yùn)行Program2涛漂,系統(tǒng)就不需要重新加載Lib.o了,而是直接鏈接检诗。

番外

xcode的編譯器經(jīng)歷了三個(gè)階段的發(fā)展:

  • GCC

GCC(GNU Compiler Collection匈仗,GNU編譯器套裝),是一套由 GNU 開發(fā)的編程語(yǔ)言編譯器岁诉,它十分的龐大锚沸,可以處理C跋选、C++涕癣、Fortran、Pascal前标、Objective-C坠韩、Java, 以及 Ada與其他語(yǔ)言。

  • LLVM

LLVM是使用GCC作為前端來(lái)對(duì)用戶程序進(jìn)行語(yǔ)義分析產(chǎn)生IF(Intermidiate Format)炼列,然后LLVM使用分析結(jié)果完成代碼優(yōu)化和生成只搁。

  • LLVM compliler(clang)

Clang只支持C,C++和Objective-C三種C家族語(yǔ)言俭尖,而前段也由GCC完全替換成了Clang氢惋,相對(duì)來(lái)說(shuō)效率更高,內(nèi)存占用也更小稽犁。

下面這張圖將顯示GCC焰望、LLVM-GCC、LLVM Compiler這三個(gè)編譯選項(xiàng)的不同點(diǎn):


image

寫在末尾:

本篇博客只是籠統(tǒng)的介紹了一下程序的運(yùn)行過(guò)程已亥,實(shí)際上程序的運(yùn)行過(guò)程非常復(fù)雜熊赖,涉及到操作系統(tǒng)、編譯原理虑椎、計(jì)算機(jī)組成原理等知識(shí)震鹉,所以想要更加深入的了解這方面知識(shí)俱笛,推薦程序員的自我修養(yǎng)這本書。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末传趾,一起剝皮案震驚了整個(gè)濱河市迎膜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浆兰,老刑警劉巖星虹,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異镊讼,居然都是意外死亡宽涌,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蝶棋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)卸亮,“玉大人,你說(shuō)我怎么就攤上這事玩裙〖婷常” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵吃溅,是天一觀的道長(zhǎng)溶诞。 經(jīng)常有香客問(wèn)我,道長(zhǎng)决侈,這世上最難降的妖魔是什么螺垢? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮赖歌,結(jié)果婚禮上枉圃,老公的妹妹穿的比我還像新娘。我一直安慰自己庐冯,他們只是感情好孽亲,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著展父,像睡著了一般返劲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上栖茉,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天篮绿,我揣著相機(jī)與錄音,去河邊找鬼衡载。 笑死搔耕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弃榨,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼菩收,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了鲸睛?” 一聲冷哼從身側(cè)響起娜饵,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎官辈,沒(méi)想到半個(gè)月后箱舞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拳亿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年晴股,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肺魁。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡电湘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鹅经,到底是詐尸還是另有隱情寂呛,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布瘾晃,位于F島的核電站贷痪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蹦误。R本人自食惡果不足惜劫拢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胖缤。 院中可真熱鬧尚镰,春花似錦阀圾、人聲如沸哪廓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)涡真。三九已至,卻和暖如春肾筐,著一層夾襖步出監(jiān)牢的瞬間哆料,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工吗铐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留东亦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像典阵,于是被迫代替她去往敵國(guó)和親奋渔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子载萌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容