程序是怎樣跑起來的
本文主要學(xué)習(xí)自 《c程序是怎樣跑起來》一書凿将,再添加了一些自己的理解和注釋,請(qǐng)各位觀者支持原版
CPU 對(duì)于程序員的意義
- 指示計(jì)算機(jī)每一步動(dòng)作的一組指令叫做程序价脾,程序是由指令和數(shù)據(jù)構(gòu)成的牧抵,
- 正在運(yùn)行的程序是存儲(chǔ)在內(nèi)存中的,磁盤和硬盤等媒介中保存的程序被復(fù)制到內(nèi)存后才能運(yùn)行侨把,
- 內(nèi)存中犀变,用來表示命令和數(shù)據(jù)存儲(chǔ)位置的數(shù)值叫做內(nèi)存地址,地址由整數(shù)值來表示. CPU 負(fù)責(zé)程序的解釋和運(yùn)行秋柄。
- CPU 能夠識(shí)別和執(zhí)行的只有機(jī)器語言获枝,使用C 或者 Java.編寫的代碼,最終都會(huì)被轉(zhuǎn)化成機(jī)器語言骇笔。
作為一個(gè)程序員只知道 CPU 是中央處理器是不夠的省店,還需要知道 CPU 是如何運(yùn)行的,特別是要弄清楚負(fù)責(zé)保存指令和數(shù)據(jù)的寄存器的機(jī)制笨触,了解了寄存器萨西,就會(huì)理解程序的運(yùn)行機(jī)制。
CPU 的內(nèi)部結(jié)構(gòu)解析
程序運(yùn)行的一般流程
- 程序員用 C 語言等高級(jí)語言編寫程序
int a;
a = 1+2;
printf("%d",a);
- 將程序編譯后轉(zhuǎn)換成機(jī)器語言的 EXE 文件
010000101001010101010010101010100101010101010010101
- 程序運(yùn)行時(shí)旭旭,在內(nèi)存中生成 EXE 文件的副本
- cpu 解釋并執(zhí)行程序內(nèi)容
CPU 內(nèi)部組成
寄存器
用來暫存指令谎脯,數(shù)據(jù)等處理對(duì)象,可以將它看成內(nèi)存持寄,一個(gè) CPU 內(nèi)部?jī)?nèi)部會(huì)有 20-100 個(gè)寄存器
控制器
負(fù)責(zé)把內(nèi)存上的指令源梭,數(shù)據(jù)讀入寄存器,并根據(jù)指令的執(zhí)行結(jié)果來控制整個(gè)計(jì)算機(jī)稍味。废麻、
運(yùn)算器
負(fù)責(zé)運(yùn)算從內(nèi)存讀入寄存器的數(shù)據(jù)
時(shí)鐘
負(fù)責(zé)發(fā)出 CPU 開始計(jì)時(shí)的時(shí)鐘信號(hào)
內(nèi)存
通常說的內(nèi)存是指計(jì)算機(jī)的主存儲(chǔ)器,簡(jiǎn)稱 主存模庐,主存通過控制芯片等與 cpu 相連烛愧,主要負(fù)責(zé)存儲(chǔ)指令和數(shù)據(jù),主存由可讀寫的元素構(gòu)成,每個(gè)字節(jié)(1個(gè)字節(jié) =8 位)都帶有一個(gè)地址編號(hào)怜姿,CPU 可以通過改地址讀取主存中的指令和數(shù)據(jù)慎冤,當(dāng)然也可以寫入數(shù)據(jù),但是需要注意的是沧卢,主存中存儲(chǔ)的指令和數(shù)據(jù)會(huì)隨著計(jì)算機(jī)的關(guān)機(jī)而自動(dòng)清除蚁堤。
程序的運(yùn)行機(jī)制:程序啟動(dòng)后,根據(jù)時(shí)鐘信號(hào)但狭,控制器會(huì)從內(nèi)存中讀取指令和數(shù)據(jù)披诗,通過這些指令加以解釋和運(yùn)行,運(yùn)算器會(huì)對(duì)數(shù)據(jù)進(jìn)行運(yùn)算立磁,控制器根據(jù)該運(yùn)算結(jié)果來控制計(jì)算機(jī)(所謂的控制就是值數(shù)據(jù)運(yùn)算以外的處理,比如:數(shù)據(jù)輸入和輸出事件的控制呈队,鍵盤,顯示器等的輸入輸出唱歧。)
CPU 是寄存器的集合體
CPU 的四個(gè)構(gòu)成部分中宪摧,其實(shí)我們只需要了解寄存器即可。原因是:程序把寄存器作為對(duì)象來描述的迈喉。
備注: 內(nèi)存的存儲(chǔ)場(chǎng)所通過地址編號(hào)來區(qū)分五慈,而寄存器的種類則通過名字來區(qū)分水泉。
通池罢可以將寄存器分為8類:
- 累加寄存器: 存儲(chǔ)執(zhí)行運(yùn)算的數(shù)據(jù)和運(yùn)算后的數(shù)據(jù) (1個(gè))
- 標(biāo)志寄存器: 存儲(chǔ)運(yùn)算處理后的 CPU 狀態(tài) (1個(gè))
- 程序計(jì)數(shù)器: 存儲(chǔ)下一條指令所在的內(nèi)存的地址 (1個(gè))
- 基址寄存器: 存儲(chǔ)數(shù)據(jù)內(nèi)存的起始地址
- 變址寄存器: 存儲(chǔ)基址寄存器的相對(duì)地址
- 通用寄存器: 存儲(chǔ)任意數(shù)據(jù)
- 指令寄存器: 存儲(chǔ)指令俭令, CPU 內(nèi)部使用幻枉,程序員無法通過程序?qū)υ摷拇嫫鬟M(jìn)行讀寫操作箕母。(1個(gè))
- 棧寄存器: 存儲(chǔ)棧區(qū)域的起始地址 (1個(gè))
決定程序流程的程序計(jì)數(shù)器
順序執(zhí)行程序
條件分枝和循環(huán)機(jī)制
程序的流程分為: 順序執(zhí)行赢赊,條件分枝和循環(huán)三種鹅心,順序執(zhí)行:是指按照地址內(nèi)容的順序執(zhí)行指令锅移,條件分枝:是指根據(jù)條件執(zhí)行任意地址的指令熔掺,循環(huán): 是指重復(fù)執(zhí)行同一地址的指令。
CPU 在進(jìn)行運(yùn)算時(shí)非剃,標(biāo)志寄存器的數(shù)值會(huì)根據(jù)運(yùn)算結(jié)果自動(dòng)設(shè)定置逻,至于是否執(zhí)行跳轉(zhuǎn)指令,則由 CPU 在參考標(biāo)志寄存器的數(shù)值后進(jìn)行判斷
cpu 執(zhí)行比較的機(jī)制:將要比較的兩個(gè)寄存器中的數(shù)據(jù)做減法备绽,得到的結(jié)果保存到標(biāo)志寄存器中券坞,判斷正負(fù)。
函數(shù)的調(diào)用機(jī)制
函數(shù)的調(diào)用是通過把程序計(jì)數(shù)器的值設(shè)定成函數(shù)的存儲(chǔ)地址來實(shí)現(xiàn)肺素。函數(shù)的調(diào)用需要再完成函數(shù)內(nèi)部的處理后恨锚,處理流程在返回到函數(shù)的調(diào)用點(diǎn)(函數(shù)調(diào)用指令的下一個(gè)地址),因此倍靡,如果只是跳轉(zhuǎn)到函數(shù)的入口地址猴伶,處理流程就不知道應(yīng)該返回至哪里了。
機(jī)器語言的 call
指令和 return
指令能夠解決這個(gè)問題解決當(dāng)函數(shù)調(diào)用后執(zhí)行的返回。函數(shù)調(diào)用使用的是 call 指令
而不是跳轉(zhuǎn)指令他挎,在將函數(shù)的入口地址設(shè)定到程序計(jì)數(shù)器之前筝尾, call 指令
會(huì)把調(diào)用函數(shù)后要執(zhí)行的指令地址存儲(chǔ)在名為棧的主存中,函數(shù)處理完畢后雇盖,再通過函數(shù)的出口來執(zhí)行 return 命令忿等, return 命令的功能是把保存在棧中的地址設(shè)定到程序計(jì)數(shù)器中。
具體請(qǐng)參考我稍后會(huì)帶來的遞歸算法的處理原理
通過地址和索引實(shí)現(xiàn)數(shù)組
通過基址寄存器和變址寄存器可以對(duì)主存上特定的內(nèi)存區(qū)域進(jìn)行劃分崔挖。從而實(shí)現(xiàn)類似于數(shù)組的操作贸街。
如果想要像數(shù)組一樣分割特定的內(nèi)存區(qū)域以達(dá)到連續(xù)查看的目的,使用兩個(gè)寄存器會(huì)更方便些狸相。 CPU 會(huì)把 基址寄存器 + 變址寄存器的值解釋為實(shí)際查看的內(nèi)存地址薛匪,變址寄存器的值就相當(dāng)于變成語言中數(shù)組的索引。
機(jī)器語言的主要類型和功能
數(shù)據(jù)是通過二進(jìn)制表示的
原碼脓鹃,反碼逸尖,補(bǔ)碼,移碼
正數(shù): 反碼 = 原碼 = 補(bǔ)碼
負(fù)數(shù): 反碼 = 其原碼除符號(hào)之外的各位求反
補(bǔ)碼 = 反碼 + 1
運(yùn)算實(shí)例
正零: 00000000
負(fù)零: 10000000
補(bǔ)碼:
00000000 11111111+1 -> 00000000
需要注意的是:如果 +1 之后有進(jìn)位瘸右,要一直往前進(jìn)位娇跟,包括符號(hào)位。
例如:
原碼: 01011
反碼: 01011
補(bǔ)碼: 01011
原碼: 11011
反碼: 10100
補(bǔ)碼: 10101
在計(jì)算機(jī)中太颤,數(shù)據(jù)一律通過補(bǔ)碼來存儲(chǔ)
使用補(bǔ)碼的原因
學(xué)習(xí)自 原碼苞俘、反碼和補(bǔ)碼, 補(bǔ)碼、負(fù)數(shù)和減法
我總結(jié)一下大概的原因如下:
- 計(jì)算機(jī)里面只有加法器龄章,沒有減法器吃谣,所有的減法都必須使用加法來進(jìn)行。
- 所謂的補(bǔ)碼算法是有編譯器在編譯的時(shí)候處理負(fù)數(shù)用的做裙。
- 使用補(bǔ)碼的原因是模運(yùn)算. 具體可以解釋為 補(bǔ)碼 = 模 - |X|
- 模 1 + 運(yùn)算位數(shù)個(gè)0
- 將二進(jìn)制的值求反后加1的結(jié)果和原來的值相加岗憋,結(jié)果為0.
數(shù)值,字符串和圖像信息在計(jì)算機(jī)內(nèi)都是以二進(jìn)制數(shù)值的形式來表現(xiàn)的
用二進(jìn)制表示計(jì)算機(jī)信息
原因
IC 是集成電路锚贱,IC 的引腳只有良種繁育狀態(tài) 0V 和 5V, 也就是說仔戈,一個(gè)引腳只能表示兩個(gè)狀態(tài)。這種特性決定了計(jì)算機(jī)只能使用二進(jìn)制數(shù)來處理信息數(shù)據(jù)拧廊。
字節(jié)是最基本的信息計(jì)量單位杂穷,位是最小單位,字節(jié)是基本單位卦绣。
除法運(yùn)算和移位運(yùn)算的關(guān)系
移位運(yùn)算就是將二進(jìn)制數(shù)值的各數(shù)位進(jìn)行左右移動(dòng)的運(yùn)算耐量,移位有左移和右移兩種,再一次運(yùn)算中滤港,可以進(jìn)行多個(gè)數(shù)位的移位操作廊蜒。
計(jì)算機(jī)進(jìn)行小數(shù)運(yùn)算時(shí)出錯(cuò)的原因
舉例查看
float sum;
int i;
sum = 0;
for (i = 0; i <100; ++i)
{
sum += 0.1;
}
printf("%f\n", sum);
打印出來竟然是 10.000002, 而不是10.
原因是什么呢趴拧,下面就來分析分析
用二進(jìn)制數(shù)表示小數(shù)
1011.0011 -- 十進(jìn)制 --> 11.1875
計(jì)算機(jī)運(yùn)算出錯(cuò)的原因
計(jì)算機(jī)之所以會(huì)出現(xiàn)運(yùn)算錯(cuò)誤的原因是因?yàn)橐恍┬?shù)無法轉(zhuǎn)換二進(jìn)制數(shù),例如上述的 0.1 山叮,就無法用二進(jìn)制數(shù)正確表示著榴,小數(shù)點(diǎn)后面即使有幾百位也無法表示。
內(nèi)存
高級(jí)語言中數(shù)據(jù)類型表示的是: 占據(jù)內(nèi)存區(qū)域的大小和存儲(chǔ)在該區(qū)域的數(shù)據(jù)類型
計(jì)算中是進(jìn)行數(shù)據(jù)處理的設(shè)備屁倔,而程序表示的就是處理順序和數(shù)據(jù)結(jié)構(gòu)脑又,由于處理對(duì)象數(shù)據(jù)是存儲(chǔ)在內(nèi)存和磁盤上的,因此程序必須能自由地使用內(nèi)存和磁盤锐借。
內(nèi)存的物理機(jī)制
內(nèi)存實(shí)際上是一種名為內(nèi)存 IC 的電子元件问麸,內(nèi)存 IC 中有電源,地址信號(hào)钞翔,數(shù)據(jù)信號(hào)严卖,控制信號(hào)等用于輸入輸出的大量引腳(IC 的引腳),通過為其制定地址布轿,來進(jìn)行數(shù)據(jù)的讀寫哮笆。
內(nèi)存的邏輯
雖然內(nèi)存的實(shí)體是內(nèi)存 IC, 但是在程序員眼里的內(nèi)存模型中,還包含著物理內(nèi)存中不存在的概念-> 數(shù)據(jù)類型汰扭。
編程語言中的數(shù)據(jù)類型表示存儲(chǔ)的是何種類型的數(shù)據(jù)稠肘,從內(nèi)存上來看,就是占用的內(nèi)存大小,即使是物理上以字節(jié)為單位來逐一讀寫的數(shù)據(jù)的內(nèi)存萝毛,在程序中项阴,通過為其制定類型,也能實(shí)現(xiàn)以特定字節(jié)數(shù)為單位來進(jìn)行讀寫珊泳。
char a;
short b;
long c;
a = 123;
b = 123;
c = 123;
這三個(gè)變量的數(shù)據(jù)都是123鲁冯,但所占用的內(nèi)存不一樣拷沸,
指針
指針是 C 語言的重要特征色查,理解指針的關(guān)鍵點(diǎn)是弄清楚數(shù)據(jù)類型這個(gè)概念。
指針也是一種變量撞芍,他所表示的不是數(shù)據(jù)的值秧了,而是存儲(chǔ)著數(shù)據(jù)的內(nèi)存的地址,通過使用指針序无,就可以對(duì)任意地址的數(shù)據(jù)進(jìn)行讀寫验毡。
char *d;
short *e;
long *f;
假設(shè) d,e,f 的值都是 100, 再這種情況下,使用 d 時(shí)就能夠從編號(hào) 100 的地址讀寫1個(gè)字段帝嗡,使用 e 時(shí)就是兩個(gè)字段晶通,f 就是4個(gè)字節(jié)。
數(shù)組是高效實(shí)用內(nèi)存的基礎(chǔ)
數(shù)組是多個(gè)同樣數(shù)據(jù)類型的數(shù)據(jù)在內(nèi)存中連續(xù)排列的形式哟玷,作為數(shù)組元素的各個(gè)數(shù)據(jù)會(huì)通過連續(xù)的編號(hào)被區(qū)分開來狮辽。這個(gè)編號(hào)稱為索引一也。制定索引后,就可以對(duì)該索引所對(duì)應(yīng)的地址的內(nèi)存進(jìn)行讀寫操作喉脖,而索引和內(nèi)存地址的變換工作則是由編譯器自動(dòng)實(shí)現(xiàn)的椰苟。
之所以說 數(shù)組是內(nèi)存的使用方法的基礎(chǔ),是因?yàn)閿?shù)組和內(nèi)存的物理構(gòu)造是一樣的树叽。
棧舆蝴,隊(duì)列以及環(huán)形緩沖區(qū)
棧和隊(duì)列,都可以不通過制定地址和索引來對(duì)數(shù)組的元素進(jìn)行讀寫题诵,需要臨時(shí)保存計(jì)算過程中的數(shù)據(jù)洁仗,連接在計(jì)算機(jī)上的設(shè)備或者輸入輸出的數(shù)據(jù)時(shí),都可以通過這些方法來使用內(nèi)存.
棧和隊(duì)列的區(qū)別就是數(shù)據(jù)出入的順序是不同的仇轻,在對(duì)內(nèi)存數(shù)據(jù)進(jìn)行讀寫時(shí)京痢,棧使用先進(jìn)后出,隊(duì)列使用先進(jìn)先出篷店。
隊(duì)列一般是以環(huán)形緩沖區(qū)的方式來實(shí)現(xiàn)的祭椰。