- 轉(zhuǎn) 原創(chuàng)地址 https://juejin.im/post/5e1e8ca2f265da3e11359d2a
CPU
還不了解 CPU 嗎?現(xiàn)在就帶你了解一下 CPU 是什么
CPU 的全稱(chēng)是 Central Processing Unit
尸执,它是你的電腦中最硬核
的組件家凯,這種說(shuō)法一點(diǎn)不為過(guò)。CPU 是能夠讓你的計(jì)算機(jī)叫計(jì)算機(jī)
的核心組件如失,但是它卻不能代表你的電腦绊诲,CPU 與計(jì)算機(jī)的關(guān)系就相當(dāng)于大腦和人的關(guān)系。CPU 的核心是從程序或應(yīng)用程序獲取指令并執(zhí)行計(jì)算褪贵。此過(guò)程可以分為三個(gè)關(guān)鍵階段:提取掂之,解碼和執(zhí)行抗俄。CPU從系統(tǒng)的主存中提取指令,然后解碼該指令的實(shí)際內(nèi)容世舰,然后再由 CPU 的相關(guān)部分執(zhí)行該指令动雹。
CPU 內(nèi)部處理過(guò)程
下圖展示了一般程序的運(yùn)行流程(以 C 語(yǔ)言為例),可以說(shuō)了解程序的運(yùn)行流程是掌握程序運(yùn)行機(jī)制的基礎(chǔ)和前提跟压。
在這個(gè)流程中胰蝠,CPU 負(fù)責(zé)的就是解釋和運(yùn)行最終轉(zhuǎn)換成機(jī)器語(yǔ)言的內(nèi)容。
CPU 主要由兩部分構(gòu)成:控制單元
和 算術(shù)邏輯單元(ALU)
- 控制單元:從內(nèi)存中提取指令并解碼執(zhí)行
- 算數(shù)邏輯單元(ALU):處理算數(shù)和邏輯運(yùn)算
CPU 是計(jì)算機(jī)的心臟和大腦震蒋,它和內(nèi)存都是由許多晶體管組成的電子部件茸塞。它接收數(shù)據(jù)輸入,執(zhí)行指令并處理信息查剖。它與輸入/輸出(I / O)設(shè)備進(jìn)行通信钾虐,這些設(shè)備向 CPU 發(fā)送數(shù)據(jù)和從 CPU 接收數(shù)據(jù)。
從功能來(lái)看梗搅,CPU 的內(nèi)部由寄存器禾唁、控制器、運(yùn)算器和時(shí)鐘四部分組成无切,各部分之間通過(guò)電信號(hào)連通荡短。
-
寄存器
是中央處理器內(nèi)的組成部分。它們可以用來(lái)暫存指令哆键、數(shù)據(jù)和地址掘托。可以將其看作是內(nèi)存的一種籍嘹。根據(jù)種類(lèi)的不同闪盔,一個(gè) CPU 內(nèi)部會(huì)有 20 - 100個(gè)寄存器。 -
控制器
負(fù)責(zé)把內(nèi)存上的指令辱士、數(shù)據(jù)讀入寄存器泪掀,并根據(jù)指令的結(jié)果控制計(jì)算機(jī) -
運(yùn)算器
負(fù)責(zé)運(yùn)算從內(nèi)存中讀入寄存器的數(shù)據(jù) -
時(shí)鐘
負(fù)責(zé)發(fā)出 CPU 開(kāi)始計(jì)時(shí)的時(shí)鐘信號(hào)
CPU 是一系列寄存器的集合體
在 CPU 的四個(gè)結(jié)構(gòu)中,我們程序員只需要了解寄存器
就可以了颂碘,其余三個(gè)不用過(guò)多關(guān)注异赫,為什么這么說(shuō)?因?yàn)槌绦蚴前鸭拇嫫髯鳛閷?duì)象來(lái)描述的头岔。
不同類(lèi)型的 CPU 塔拳,其內(nèi)部寄存器的種類(lèi),數(shù)量以及寄存器存儲(chǔ)的數(shù)值范圍都是不同的峡竣。不過(guò)靠抑,根據(jù)功能的不同,可以將寄存器劃分為下面這幾類(lèi)
種類(lèi) | 功能 |
---|---|
累加寄存器 | 存儲(chǔ)運(yùn)行的數(shù)據(jù)和運(yùn)算后的數(shù)據(jù)适掰。 |
標(biāo)志寄存器 | 用于反應(yīng)處理器的狀態(tài)和運(yùn)算結(jié)果的某些特征以及控制指令的執(zhí)行颂碧。 |
程序計(jì)數(shù)器 | 程序計(jì)數(shù)器是用于存放下一條指令所在單元的地址的地方荠列。 |
基址寄存器 | 存儲(chǔ)數(shù)據(jù)內(nèi)存的起始位置 |
變址寄存器 | 存儲(chǔ)基址寄存器的相對(duì)地址 |
通用寄存器 | 存儲(chǔ)任意數(shù)據(jù) |
指令寄存器 | 儲(chǔ)存正在被運(yùn)行的指令,CPU內(nèi)部使用稚伍,程序員無(wú)法對(duì)該寄存器進(jìn)行讀寫(xiě) |
棧寄存器 | 存儲(chǔ)棧區(qū)域的起始位置 |
其中程序計(jì)數(shù)器抛猫、累加寄存器谜叹、標(biāo)志寄存器、指令寄存器和棧寄存器都只有一個(gè),其他寄存器一般有多個(gè)啥箭。
下面就對(duì)各個(gè)寄存器進(jìn)行說(shuō)明
程序計(jì)數(shù)器
程序計(jì)數(shù)器(Program Counter)
是用來(lái)存儲(chǔ)下一條指令所在單元的地址酒繁。
程序執(zhí)行時(shí)皮钠,PC的初值為程序第一條指令的地址识樱,在順序執(zhí)行程序時(shí),控制器
首先按程序計(jì)數(shù)器所指出的指令地址從內(nèi)存中取出一條指令猴贰,然后分析和執(zhí)行該指令对雪,同時(shí)將PC的值加1指向下一條要執(zhí)行的指令。
我們還是以一個(gè)事例為準(zhǔn)來(lái)詳細(xì)的看一下程序計(jì)數(shù)器的執(zhí)行過(guò)程
這是一段進(jìn)行相加的操作米绕,程序啟動(dòng)瑟捣,在經(jīng)過(guò)編譯解析后會(huì)由操作系統(tǒng)把硬盤(pán)中的程序復(fù)制到內(nèi)存中,示例中的程序是將 123 和 456 執(zhí)行相加操作栅干,并將結(jié)果輸出到顯示器上迈套。
地址 0100
是程序運(yùn)行的起始位置。Windows 等操作系統(tǒng)把程序從硬盤(pán)復(fù)制到內(nèi)存后碱鳞,會(huì)將程序計(jì)數(shù)器作為設(shè)定為起始位置 0100桑李,然后執(zhí)行程序,每執(zhí)行一條指令后窿给,程序計(jì)數(shù)器的數(shù)值會(huì)增加1(或者直接指向下一條指令的地址)贵白,然后,CPU 就會(huì)根據(jù)程序計(jì)數(shù)器的數(shù)值崩泡,從內(nèi)存中讀取命令并執(zhí)行禁荒,也就是說(shuō),程序計(jì)數(shù)器控制著程序的流程角撞。
條件分支和循環(huán)機(jī)制
高級(jí)語(yǔ)言中的條件控制流程主要分為三種:順序執(zhí)行圈浇、條件分支、循環(huán)判斷
三種靴寂,順序執(zhí)行是按照地址的內(nèi)容順序的執(zhí)行指令。條件分支是根據(jù)條件執(zhí)行任意地址的指令召耘。循環(huán)是重復(fù)執(zhí)行同一地址的指令百炬。
- 順序執(zhí)行的情況比較簡(jiǎn)單,每執(zhí)行一條指令程序計(jì)數(shù)器的值就是 + 1污它。
- 條件和循環(huán)分支會(huì)使程序計(jì)數(shù)器的值指向任意的地址剖踊,這樣一來(lái)庶弃,程序便可以返回到上一個(gè)地址來(lái)重復(fù)執(zhí)行同一個(gè)指令,或者跳轉(zhuǎn)到任意指令德澈。
下面以條件分支為例來(lái)說(shuō)明程序的執(zhí)行過(guò)程(循環(huán)也很相似)
程序的開(kāi)始過(guò)程和順序流程是一樣的歇攻,CPU 從0100處開(kāi)始執(zhí)行命令,在0100和0101都是順序執(zhí)行梆造,PC 的值順序+1缴守,執(zhí)行到0102地址的指令時(shí),判斷0106寄存器的數(shù)值大于0镇辉,跳轉(zhuǎn)(jump)到0104地址的指令屡穗,將數(shù)值輸出到顯示器中,然后結(jié)束程序忽肛,0103 的指令被跳過(guò)了村砂,這就和我們程序中的 if()
判斷是一樣的,在不滿(mǎn)足條件的情況下屹逛,指令會(huì)直接跳過(guò)础废。所以 PC 的執(zhí)行過(guò)程也就沒(méi)有直接+1,而是下一條指令的地址罕模。
標(biāo)志寄存器
條件和循環(huán)分支會(huì)使用到 jump(跳轉(zhuǎn)指令)
评腺,會(huì)根據(jù)當(dāng)前的指令來(lái)判斷是否跳轉(zhuǎn),上面我們提到了標(biāo)志寄存器
手销,無(wú)論當(dāng)前累加寄存器的運(yùn)算結(jié)果是正數(shù)歇僧、負(fù)數(shù)還是零,標(biāo)志寄存器都會(huì)將其保存
CPU 在進(jìn)行運(yùn)算時(shí)锋拖,標(biāo)志寄存器的數(shù)值會(huì)根據(jù)當(dāng)前運(yùn)算的結(jié)果自動(dòng)設(shè)定诈悍,運(yùn)算結(jié)果的正、負(fù)和零三種狀態(tài)由標(biāo)志寄存器的三個(gè)位表示兽埃。標(biāo)志寄存器的第一個(gè)字節(jié)位侥钳、第二個(gè)字節(jié)位、第三個(gè)字節(jié)位各自的結(jié)果都為1時(shí)柄错,分別代表著正數(shù)舷夺、零和負(fù)數(shù)。
CPU 的執(zhí)行機(jī)制比較有意思售貌,假設(shè)累加寄存器中存儲(chǔ)的 XXX 和通用寄存器中存儲(chǔ)的 YYY 做比較给猾,執(zhí)行比較的背后,CPU 的運(yùn)算機(jī)制就會(huì)做減法運(yùn)算颂跨。而無(wú)論減法運(yùn)算的結(jié)果是正數(shù)敢伸、零還是負(fù)數(shù),都會(huì)保存到標(biāo)志寄存器中恒削。結(jié)果為正表示 XXX 比 YYY 大池颈,結(jié)果為零表示 XXX 和 YYY 相等尾序,結(jié)果為負(fù)表示 XXX 比 YYY 小。程序比較的指令躯砰,實(shí)際上是在 CPU 內(nèi)部做減法
運(yùn)算每币。
函數(shù)調(diào)用機(jī)制
接下來(lái),我們繼續(xù)介紹函數(shù)調(diào)用機(jī)制琢歇,哪怕是高級(jí)語(yǔ)言編寫(xiě)的程序兰怠,函數(shù)調(diào)用處理也是通過(guò)把程序計(jì)數(shù)器的值設(shè)定成函數(shù)的存儲(chǔ)地址來(lái)實(shí)現(xiàn)的。函數(shù)執(zhí)行跳轉(zhuǎn)指令后矿微,必須進(jìn)行返回處理痕慢,單純的指令跳轉(zhuǎn)沒(méi)有意義,下面是一個(gè)實(shí)現(xiàn)函數(shù)跳轉(zhuǎn)的例子
圖中將變量 a 和 b 分別賦值為 123 和 456 涌矢,調(diào)用 MyFun(a,b) 方法掖举,進(jìn)行指令跳轉(zhuǎn)。圖中的地址是將 C 語(yǔ)言編譯成機(jī)器語(yǔ)言后運(yùn)行時(shí)的地址娜庇,由于1行 C 程序在編譯后通常會(huì)變?yōu)槎嘈袡C(jī)器語(yǔ)言塔次,所以圖中的地址是分散的。在執(zhí)行完 MyFun(a,b)指令后名秀,程序會(huì)返回到 MyFun(a,b) 的下一條指令励负,CPU 繼續(xù)執(zhí)行下面的指令。
函數(shù)的調(diào)用和返回很重要的兩個(gè)指令是 call
和 return
指令匕得,再將函數(shù)的入口地址設(shè)定到程序計(jì)數(shù)器之前继榆,call 指令會(huì)把調(diào)用函數(shù)后要執(zhí)行的指令地址存儲(chǔ)在名為棧的主存內(nèi)。函數(shù)處理完畢后汁掠,再通過(guò)函數(shù)的出口來(lái)執(zhí)行 return 指令略吨。return 指令的功能是把保存在棧中的地址設(shè)定到程序計(jì)數(shù)器。MyFun 函數(shù)在被調(diào)用之前考阱,0154 地址保存在棧中翠忠,MyFun 函數(shù)處理完成后,會(huì)把 0154 的地址保存在程序計(jì)數(shù)器中乞榨。這個(gè)調(diào)用過(guò)程如下
在一些高級(jí)語(yǔ)言的條件或者循環(huán)語(yǔ)句中秽之,函數(shù)調(diào)用的處理會(huì)轉(zhuǎn)換成 call 指令,函數(shù)結(jié)束后的處理則會(huì)轉(zhuǎn)換成 return 指令吃既。
通過(guò)地址和索引實(shí)現(xiàn)數(shù)組
接下來(lái)我們看一下基址寄存器和變址寄存器考榨,通過(guò)這兩個(gè)寄存器,我們可以對(duì)主存上的特定區(qū)域進(jìn)行劃分鹦倚,來(lái)實(shí)現(xiàn)類(lèi)似數(shù)組的操作董虱,首先,我們用十六進(jìn)制數(shù)將計(jì)算機(jī)內(nèi)存上的 00000000 - FFFFFFFF 的地址劃分出來(lái)。那么愤诱,凡是該范圍的內(nèi)存地址,只要有一個(gè) 32 位的寄存器捐友,便可查看全部地址淫半。但如果想要想數(shù)組那樣分割特定的內(nèi)存區(qū)域以達(dá)到連續(xù)查看的目的的話(huà),使用兩個(gè)寄存器會(huì)更加方便匣砖。
例如科吭,我們用兩個(gè)寄存器(基址寄存器和變址寄存器)來(lái)表示內(nèi)存的值
這種表示方式很類(lèi)似數(shù)組的構(gòu)造,數(shù)組
是指同樣長(zhǎng)度的數(shù)據(jù)在內(nèi)存中進(jìn)行連續(xù)排列的數(shù)據(jù)構(gòu)造猴鲫。用數(shù)組名表示數(shù)組全部的值对人,通過(guò)索引來(lái)區(qū)分?jǐn)?shù)組的各個(gè)數(shù)據(jù)元素,例如: a[0] - a[4]拂共,[]
內(nèi)的 0 - 4 就是數(shù)組的下標(biāo)牺弄。
CPU 指令執(zhí)行過(guò)程
幾乎所有的馮·諾伊曼型計(jì)算機(jī)的CPU,其工作都可以分為5個(gè)階段:取指令宜狐、指令譯碼势告、執(zhí)行指令、訪(fǎng)存取數(shù)抚恒、結(jié)果寫(xiě)回咱台。
-
取指令
階段是將內(nèi)存中的指令讀取到 CPU 中寄存器的過(guò)程,程序寄存器用于存儲(chǔ)下一條指令所在的地址 -
指令譯碼
階段俭驮,在取指令完成后回溺,立馬進(jìn)入指令譯碼階段,在指令譯碼階段混萝,指令譯碼器按照預(yù)定的指令格式遗遵,對(duì)取回的指令進(jìn)行拆分和解釋?zhuān)R(shí)別區(qū)分出不同的指令類(lèi)別以及各種獲取操作數(shù)的方法。 -
執(zhí)行指令
階段譬圣,譯碼完成后瓮恭,就需要執(zhí)行這一條指令了,此階段的任務(wù)是完成指令所規(guī)定的各種操作厘熟,具體實(shí)現(xiàn)指令的功能屯蹦。 -
訪(fǎng)問(wèn)取數(shù)
階段,根據(jù)指令的需要绳姨,有可能需要從內(nèi)存中提取數(shù)據(jù)登澜,此階段的任務(wù)是:根據(jù)指令地址碼,得到操作數(shù)在主存中的地址飘庄,并從主存中讀取該操作數(shù)用于運(yùn)算脑蠕。 -
結(jié)果寫(xiě)回
階段,作為最后一個(gè)階段,結(jié)果寫(xiě)回(Write Back谴仙,WB)階段把執(zhí)行指令階段的運(yùn)行結(jié)果數(shù)據(jù)“寫(xiě)回”到某種存儲(chǔ)形式:結(jié)果數(shù)據(jù)經(jīng)常被寫(xiě)到CPU的內(nèi)部寄存器中迂求,以便被后續(xù)的指令快速地存取晃跺;
內(nèi)存
CPU 和 內(nèi)存就像是一堆不可分割的戀人一樣揩局,是無(wú)法拆散的一對(duì)兒,沒(méi)有內(nèi)存掀虎,CPU 無(wú)法執(zhí)行程序指令凌盯,那么計(jì)算機(jī)也就失去了意義;只有內(nèi)存烹玉,無(wú)法執(zhí)行指令驰怎,那么計(jì)算機(jī)照樣無(wú)法運(yùn)行。
那么什么是內(nèi)存呢二打??jī)?nèi)存和 CPU 如何進(jìn)行交互县忌?下面就來(lái)介紹一下
什么是內(nèi)存
內(nèi)存(Memory)是計(jì)算機(jī)中最重要的部件之一,它是程序與CPU進(jìn)行溝通的橋梁址儒。計(jì)算機(jī)中所有程序的運(yùn)行都是在內(nèi)存中進(jìn)行的芹枷,因此內(nèi)存對(duì)計(jì)算機(jī)的影響非常大,內(nèi)存又被稱(chēng)為主存
莲趣,其作用是存放 CPU 中的運(yùn)算數(shù)據(jù)鸳慈,以及與硬盤(pán)等外部存儲(chǔ)設(shè)備交換的數(shù)據(jù)。只要計(jì)算機(jī)在運(yùn)行中喧伞,CPU 就會(huì)把需要運(yùn)算的數(shù)據(jù)調(diào)到主存中進(jìn)行運(yùn)算走芋,當(dāng)運(yùn)算完成后CPU再將結(jié)果傳送出來(lái),主存的運(yùn)行也決定了計(jì)算機(jī)的穩(wěn)定運(yùn)行潘鲫。
內(nèi)存的物理結(jié)構(gòu)
內(nèi)存的內(nèi)部是由各種 IC 電路組成的翁逞,它的種類(lèi)很龐大,但是其主要分為三種存儲(chǔ)器
- 隨機(jī)存儲(chǔ)器(RAM): 內(nèi)存中最重要的一種溉仑,表示既可以從中讀取數(shù)據(jù)挖函,也可以寫(xiě)入數(shù)據(jù)。當(dāng)機(jī)器關(guān)閉時(shí)浊竟,內(nèi)存中的信息會(huì)
丟失
怨喘。 - 只讀存儲(chǔ)器(ROM):ROM 一般只能用于數(shù)據(jù)的讀取,不能寫(xiě)入數(shù)據(jù)振定,但是當(dāng)機(jī)器停電時(shí)必怜,這些數(shù)據(jù)不會(huì)丟失。
- 高速緩存(Cache):Cache 也是我們經(jīng)常見(jiàn)到的后频,它分為一級(jí)緩存(L1 Cache)梳庆、二級(jí)緩存(L2 Cache)暖途、三級(jí)緩存(L3 Cache)這些數(shù)據(jù),它位于內(nèi)存和 CPU 之間膏执,是一個(gè)讀寫(xiě)速度比內(nèi)存
更快
的存儲(chǔ)器驻售。當(dāng) CPU 向內(nèi)存寫(xiě)入數(shù)據(jù)時(shí),這些數(shù)據(jù)也會(huì)被寫(xiě)入高速緩存中胧后。當(dāng) CPU 需要讀取數(shù)據(jù)時(shí)芋浮,會(huì)直接從高速緩存中直接讀取,當(dāng)然壳快,如需要的數(shù)據(jù)在Cache中沒(méi)有,CPU會(huì)再去讀取內(nèi)存中的數(shù)據(jù)镇草。
內(nèi)存 IC 是一個(gè)完整的結(jié)構(gòu)眶痰,它內(nèi)部也有電源、地址信號(hào)梯啤、數(shù)據(jù)信號(hào)竖伯、控制信號(hào)和用于尋址的 IC 引腳來(lái)進(jìn)行數(shù)據(jù)的讀寫(xiě)。下面是一個(gè)虛擬的 IC 引腳示意圖
圖中 VCC 和 GND 表示電源因宇,A0 - A9 是地址信號(hào)的引腳七婴,D0 - D7 表示的是控制信號(hào)蜕劝、RD 和 WR 都是好控制信號(hào)嘹狞,我用不同的顏色進(jìn)行了區(qū)分,將電源連接到 VCC 和 GND 后秫逝,就可以對(duì)其他引腳傳遞 0 和 1 的信號(hào)贺辰,大多數(shù)情況下户盯,+5V 表示1,0V 表示 0饲化。
我們都知道內(nèi)存是用來(lái)存儲(chǔ)數(shù)據(jù)莽鸭,那么這個(gè)內(nèi)存 IC 中能存儲(chǔ)多少數(shù)據(jù)呢?D0 - D7 表示的是數(shù)據(jù)信號(hào)吃靠,也就是說(shuō)硫眨,一次可以輸入輸出 8 bit = 1 byte 的數(shù)據(jù)。A0 - A9 是地址信號(hào)共十個(gè)巢块,表示可以指定 00000 00000 - 11111 11111 共 2 的 10次方 = 1024個(gè)地址
礁阁。每個(gè)地址都會(huì)存放 1 byte 的數(shù)據(jù),因此我們可以得出內(nèi)存 IC 的容量就是 1 KB夕冲。
內(nèi)存的讀寫(xiě)過(guò)程
讓我們把關(guān)注點(diǎn)放在內(nèi)存 IC 對(duì)數(shù)據(jù)的讀寫(xiě)過(guò)程上來(lái)吧氮兵!我們來(lái)看一個(gè)對(duì)內(nèi)存IC 進(jìn)行數(shù)據(jù)寫(xiě)入和讀取的模型
來(lái)詳細(xì)描述一下這個(gè)過(guò)程,假設(shè)我們要向內(nèi)存 IC 中寫(xiě)入 1byte 的數(shù)據(jù)的話(huà)歹鱼,它的過(guò)程是這樣的:
- 首先給 VCC 接通 +5V 的電源泣栈,給 GND 接通 0V 的電源,使用
A0 - A9
來(lái)指定數(shù)據(jù)的存儲(chǔ)場(chǎng)所,然后再把數(shù)據(jù)的值輸入給D0 - D7
的數(shù)據(jù)信號(hào)南片,并把WR(write)
的值置為 1掺涛,執(zhí)行完這些操作后,即可以向內(nèi)存 IC 寫(xiě)入數(shù)據(jù) - 讀出數(shù)據(jù)時(shí)疼进,只需要通過(guò) A0 - A9 的地址信號(hào)指定數(shù)據(jù)的存儲(chǔ)場(chǎng)所薪缆,然后再將 RD 的值置為 1 即可。
- 圖中的 RD 和 WR 又被稱(chēng)為控制信號(hào)伞广。其中當(dāng)WR 和 RD 都為 0 時(shí)拣帽,無(wú)法進(jìn)行寫(xiě)入和讀取操作。
內(nèi)存的現(xiàn)實(shí)模型
為了便于記憶嚼锄,我們把內(nèi)存模型映射成為我們現(xiàn)實(shí)世界的模型减拭,在現(xiàn)實(shí)世界中,內(nèi)存的模型很想我們生活的樓房区丑。在這個(gè)樓房中拧粪,1層可以存儲(chǔ)一個(gè)字節(jié)的數(shù)據(jù),樓層號(hào)就是地址
沧侥,下面是內(nèi)存和樓層整合的模型圖
我們知道可霎,程序中的數(shù)據(jù)不僅只有數(shù)值,還有數(shù)據(jù)類(lèi)型
的概念宴杀,從內(nèi)存上來(lái)看癣朗,就是占用內(nèi)存大小(占用樓層數(shù))的意思婴氮。即使物理上強(qiáng)制以 1 個(gè)字節(jié)為單位來(lái)逐一讀寫(xiě)數(shù)據(jù)的內(nèi)存斯棒,在程序中,通過(guò)指定其數(shù)據(jù)類(lèi)型主经,也能實(shí)現(xiàn)以特定字節(jié)數(shù)為單位來(lái)進(jìn)行讀寫(xiě)荣暮。
二進(jìn)制
我們都知道,計(jì)算機(jī)的底層都是使用二進(jìn)制數(shù)據(jù)進(jìn)行數(shù)據(jù)流傳輸?shù)恼肿ぃ敲礊槭裁磿?huì)使用二進(jìn)制表示計(jì)算機(jī)呢穗酥?或者說(shuō),什么是二進(jìn)制數(shù)呢惠遏?在拓展一步砾跃,如何使用二進(jìn)制進(jìn)行加減乘除?下面就來(lái)看一下
什么是二進(jìn)制數(shù)
那么什么是二進(jìn)制數(shù)呢节吮?為了說(shuō)明這個(gè)問(wèn)題抽高,我們先把 00100111
這個(gè)數(shù)轉(zhuǎn)換為十進(jìn)制數(shù)看一下,二進(jìn)制數(shù)轉(zhuǎn)換為十進(jìn)制數(shù)透绩,直接將各位置上的值 * 位權(quán)即可翘骂,那么我們將上面的數(shù)值進(jìn)行轉(zhuǎn)換
也就是說(shuō)壁熄,二進(jìn)制數(shù)代表的 00100111
轉(zhuǎn)換成十進(jìn)制就是 39,這個(gè) 39 并不是 3 和 9 兩個(gè)數(shù)字連著寫(xiě)碳竟,而是 3 * 10 + 9 * 1草丧,這里面的 10 , 1
就是位權(quán),以此類(lèi)推莹桅,上述例子中的位權(quán)從高位到低位依次就是 7 6 5 4 3 2 1 0
昌执。這個(gè)位權(quán)也叫做次冪,那么最高位就是2的7次冪诈泼,2的6次冪 等等懂拾。二進(jìn)制數(shù)的運(yùn)算每次都會(huì)以2為底,這個(gè)2 指得就是基數(shù)铐达,那么十進(jìn)制數(shù)的基數(shù)也就是 10 委粉。在任何情況下位權(quán)的值都是 數(shù)的位數(shù) - 1,那么第一位的位權(quán)就是 1 - 1 = 0娶桦, 第二位的位權(quán)就睡 2 - 1 = 1,以此類(lèi)推汁汗。
那么我們所說(shuō)的二進(jìn)制數(shù)其實(shí)就是 用0和1兩個(gè)數(shù)字來(lái)表示的數(shù)衷畦,它的基數(shù)為2,它的數(shù)值就是每個(gè)數(shù)的位數(shù) * 位權(quán)再求和得到的結(jié)果知牌,我們一般來(lái)說(shuō)數(shù)值指的就是十進(jìn)制數(shù)祈争,那么它的數(shù)值就是 3 * 10 + 9 * 1 = 39。
移位運(yùn)算和乘除的關(guān)系
在了解過(guò)二進(jìn)制之后角寸,下面我們來(lái)看一下二進(jìn)制的運(yùn)算菩混,和十進(jìn)制數(shù)一樣,加減乘除也適用于二進(jìn)制數(shù)扁藕,只要注意逢 2 進(jìn)位即可沮峡。二進(jìn)制數(shù)的運(yùn)算,也是計(jì)算機(jī)程序所特有的運(yùn)算亿柑,因此了解二進(jìn)制的運(yùn)算是必須要掌握的邢疙。
首先我們來(lái)介紹移位
運(yùn)算,移位運(yùn)算是指將二進(jìn)制的數(shù)值的各個(gè)位置上的元素坐左移和右移操作望薄,見(jiàn)下圖
補(bǔ)數(shù)
剛才我們沒(méi)有介紹右移的情況疟游,是因?yàn)橛乙浦罂粘鰜?lái)的高位數(shù)值,有 0 和 1 兩種形式痕支。要想?yún)^(qū)分什么時(shí)候補(bǔ)0什么時(shí)候補(bǔ)1颁虐,首先就需要掌握二進(jìn)制數(shù)表示負(fù)數(shù)
的方法。
二進(jìn)制數(shù)中表示負(fù)數(shù)值時(shí)卧须,一般會(huì)把最高位作為符號(hào)來(lái)使用另绩,因此我們把這個(gè)最高位當(dāng)作符號(hào)位儒陨。 符號(hào)位是 0 時(shí)表示正數(shù)
,是 1 時(shí)表示 負(fù)數(shù)
板熊。那么 -1 用二進(jìn)制數(shù)該如何表示呢框全?可能很多人會(huì)這么認(rèn)為: 因?yàn)?1 的二進(jìn)制數(shù)是 0000 0001
,最高位是符號(hào)位干签,所以正確的表示 -1 應(yīng)該是 1000 0001
津辩,但是這個(gè)答案真的對(duì)嗎?
計(jì)算機(jī)世界中是沒(méi)有減法的容劳,計(jì)算機(jī)在做減法的時(shí)候其實(shí)就是在做加法喘沿,也就是用加法來(lái)實(shí)現(xiàn)的減法運(yùn)算。比如 100 - 50 竭贩,其實(shí)計(jì)算機(jī)來(lái)看的時(shí)候應(yīng)該是 100 + (-50)蚜印,為此,在表示負(fù)數(shù)的時(shí)候就要用到二進(jìn)制補(bǔ)數(shù)
留量,補(bǔ)數(shù)就是用正數(shù)來(lái)表示的負(fù)數(shù)窄赋。
為了獲得補(bǔ)數(shù)
,我們需要將二進(jìn)制的各數(shù)位的數(shù)值全部取反楼熄,然后再將結(jié)果 + 1 即可忆绰,先記住這個(gè)結(jié)論,下面我們來(lái)演示一下可岂。
具體來(lái)說(shuō)错敢,就是需要先獲取某個(gè)數(shù)值的二進(jìn)制數(shù),然后對(duì)二進(jìn)制數(shù)的每一位做取反操作(0 ---> 1 , 1 ---> 0)缕粹,最后再對(duì)取反后的數(shù) +1 稚茅,這樣就完成了補(bǔ)數(shù)的獲取。
補(bǔ)數(shù)的獲取平斩,雖然直觀(guān)上不易理解亚享,但是邏輯上卻非常嚴(yán)謹(jǐn),比如我們來(lái)看一下 1 - 1 的這個(gè)過(guò)程双戳,我們先用上面的這個(gè) 1000 0001
(它是1的補(bǔ)數(shù)虹蒋,不知道的請(qǐng)看上文,正確性先不管飒货,只是用來(lái)做一下計(jì)算)來(lái)表示一下
奇怪魄衅,1 - 1 會(huì)變成 130 ,而不是0塘辅,所以可以得出結(jié)論 1000 0001
表示 -1 是完全錯(cuò)誤的晃虫。
那么正確的該如何表示呢?其實(shí)我們上面已經(jīng)給出結(jié)果了扣墩,那就是 1111 1111
哲银,來(lái)論證一下它的正確性
我們可以看到 1 - 1 其實(shí)實(shí)際上就是 1 + (-1)扛吞,對(duì) -1 進(jìn)行上面的取反 + 1 后變?yōu)?1111 1111
, 然后與 1 進(jìn)行加法運(yùn)算,得到的結(jié)果是九位的 1 0000 0000
荆责,結(jié)果發(fā)生了溢出
滥比,計(jì)算機(jī)會(huì)直接忽略掉溢出位,也就是直接拋掉 最高位 1 做院,變?yōu)?0000 0000
盲泛。也就是 0,結(jié)果正確键耕,所以 1111 1111
表示的就是 -1 寺滚。
所以負(fù)數(shù)的二進(jìn)制表示就是先求其補(bǔ)數(shù),補(bǔ)數(shù)的求解過(guò)程就是對(duì)原始數(shù)值的二進(jìn)制數(shù)各位取反屈雄,然后將結(jié)果 + 1村视。
算數(shù)右移和邏輯右移的區(qū)別
在了解完補(bǔ)數(shù)后,我們重新考慮一下右移這個(gè)議題酒奶,右移在移位后空出來(lái)的最高位有兩種情況 0 和 1
蚁孔。
將二進(jìn)制數(shù)作為帶符號(hào)的數(shù)值進(jìn)行右移運(yùn)算時(shí),移位后需要在最高位填充移位前符號(hào)位的值( 0 或 1)惋嚎。這就被稱(chēng)為算數(shù)右移
勒虾。如果數(shù)值使用補(bǔ)數(shù)表示的負(fù)數(shù)值,那么右移后在空出來(lái)的最高位補(bǔ) 1瘸彤,就可以正確的表示 1/2,1/4,1/8
等的數(shù)值運(yùn)算。如果是正數(shù)笛钝,那么直接在空出來(lái)的位置補(bǔ) 0 即可质况。
下面來(lái)看一個(gè)右移的例子。將 -4 右移兩位玻靡,來(lái)各自看一下移位示意圖
如上圖所示结榄,在邏輯右移的情況下, -4 右移兩位會(huì)變成 63
囤捻, 顯然不是它的 1/4臼朗,所以不能使用邏輯右移,那么算數(shù)右移的情況下蝎土,右移兩位會(huì)變?yōu)?-1
视哑,顯然是它的 1/4,故而采用算數(shù)右移誊涯。
那么我們可以得出來(lái)一個(gè)結(jié)論:左移時(shí)挡毅,無(wú)論是圖形還是數(shù)值,移位后暴构,只需要將低位補(bǔ) 0 即可跪呈;右移時(shí)段磨,需要根據(jù)情況判斷是邏輯右移還是算數(shù)右移。
下面介紹一下符號(hào)擴(kuò)展:將數(shù)據(jù)進(jìn)行符號(hào)擴(kuò)展是為了產(chǎn)生一個(gè)位數(shù)加倍耗绿、但數(shù)值大小不變的結(jié)果苹支,以滿(mǎn)足有些指令對(duì)操作數(shù)位數(shù)的要求,例如倍長(zhǎng)于除數(shù)的被除數(shù)误阻,再如將數(shù)據(jù)位數(shù)加長(zhǎng)以減少計(jì)算過(guò)程中的誤差债蜜。
以8位二進(jìn)制為例,符號(hào)擴(kuò)展就是指在保持值不變的前提下將其轉(zhuǎn)換成為16位和32位的二進(jìn)制數(shù)堕绩。將0111 1111
這個(gè)正的 8位二進(jìn)制數(shù)轉(zhuǎn)換成為 16位二進(jìn)制數(shù)時(shí)策幼,很容易就能夠得出0000 0000 0111 1111
這個(gè)正確的結(jié)果,但是像 1111 1111
這樣的補(bǔ)數(shù)來(lái)表示的數(shù)值奴紧,該如何處理特姐?直接將其表示成為1111 1111 1111 1111
就可以了。也就是說(shuō)黍氮,不管正數(shù)還是補(bǔ)數(shù)表示的負(fù)數(shù)唐含,只需要將 0 和 1 填充高位即可。
內(nèi)存和磁盤(pán)的關(guān)系
我們大家知道沫浆,計(jì)算機(jī)的五大基礎(chǔ)部件是 存儲(chǔ)器
捷枯、控制器
、運(yùn)算器
专执、輸入和輸出設(shè)備
淮捆,其中從存儲(chǔ)功能的角度來(lái)看,可以把存儲(chǔ)器分為內(nèi)存
和 磁盤(pán)
本股,我們上面介紹過(guò)內(nèi)存攀痊,下面就來(lái)介紹一下磁盤(pán)以及磁盤(pán)和內(nèi)存的關(guān)系
程序不讀入內(nèi)存就無(wú)法運(yùn)行
計(jì)算機(jī)最主要的存儲(chǔ)部件是內(nèi)存和磁盤(pán)。磁盤(pán)中存儲(chǔ)的程序必須加載到內(nèi)存中才能運(yùn)行拄显,在磁盤(pán)中保存的程序是無(wú)法直接運(yùn)行的苟径,這是因?yàn)樨?fù)責(zé)解析和運(yùn)行程序內(nèi)容的 CPU 是需要通過(guò)程序計(jì)數(shù)器來(lái)指定內(nèi)存地址從而讀出程序指令的。
磁盤(pán)構(gòu)造
磁盤(pán)緩存
我們上面提到躬审,磁盤(pán)往往和內(nèi)存是互利共生的關(guān)系棘街,相互協(xié)作,彼此持有良好的合作關(guān)系承边。每次內(nèi)存都需要從磁盤(pán)中讀取數(shù)據(jù)遭殉,必然會(huì)讀到相同的內(nèi)容,所以一定會(huì)有一個(gè)角色負(fù)責(zé)存儲(chǔ)我們經(jīng)常需要讀到的內(nèi)容博助。 我們大家做軟件的時(shí)候經(jīng)常會(huì)用到緩存技術(shù)
恩沽,那么硬件層面也不例外,磁盤(pán)也有緩存翔始,磁盤(pán)的緩存叫做磁盤(pán)緩存
罗心。
磁盤(pán)緩存指的是把從磁盤(pán)中讀出的數(shù)據(jù)存儲(chǔ)到內(nèi)存的方式里伯,這樣一來(lái),當(dāng)接下來(lái)需要讀取相同的內(nèi)容時(shí)渤闷,就不會(huì)再通過(guò)實(shí)際的磁盤(pán)袖瞻,而是通過(guò)磁盤(pán)緩存來(lái)讀取享潜。某一種技術(shù)或者框架的出現(xiàn)勢(shì)必要解決某種問(wèn)題的易核,那么磁盤(pán)緩存就大大改善了磁盤(pán)訪(fǎng)問(wèn)的速度遏弱。
虛擬內(nèi)存
虛擬內(nèi)存
是內(nèi)存和磁盤(pán)交互的第二個(gè)媒介。虛擬內(nèi)存是指把磁盤(pán)的一部分作為假想內(nèi)存
來(lái)使用弦蹂。這與磁盤(pán)緩存是假想的磁盤(pán)(實(shí)際上是內(nèi)存)相對(duì)肩碟,虛擬內(nèi)存是假想的內(nèi)存(實(shí)際上是磁盤(pán))。
虛擬內(nèi)存是計(jì)算機(jī)系統(tǒng)內(nèi)存管理的一種技術(shù)凸椿。它使得應(yīng)用程序認(rèn)為它擁有連續(xù)可用
的內(nèi)存(一個(gè)完整的地址空間)削祈,但是實(shí)際上,它通常被分割成多個(gè)物理碎片脑漫,還有部分存儲(chǔ)在外部磁盤(pán)管理器上髓抑,必要時(shí)進(jìn)行數(shù)據(jù)交換。
通過(guò)借助虛擬內(nèi)存优幸,在內(nèi)存不足時(shí)仍然可以運(yùn)行程序吨拍。例如,在只剩 5MB 內(nèi)存空間的情況下仍然可以運(yùn)行 10MB 的程序网杆。由于 CPU 只能執(zhí)行加載到內(nèi)存中的程序羹饰,因此,虛擬內(nèi)存的空間就需要和內(nèi)存中的空間進(jìn)行置換(swap)
碳却,然后運(yùn)行程序严里。
虛擬內(nèi)存與內(nèi)存的交換方式
虛擬內(nèi)存的方法有分頁(yè)式
和 分段式
兩種。Windows 采用的是分頁(yè)式追城。該方式是指在不考慮程序構(gòu)造的情況下,把運(yùn)行的程序按照一定大小的頁(yè)進(jìn)行分割燥撞,并以頁(yè)
為單位進(jìn)行置換座柱。在分頁(yè)式中,我們把磁盤(pán)的內(nèi)容讀到內(nèi)存中稱(chēng)為 Page In
物舒,把內(nèi)存的內(nèi)容寫(xiě)入磁盤(pán)稱(chēng)為 Page Out
色洞。Windows 計(jì)算機(jī)的頁(yè)大小為 4KB ,也就是說(shuō)冠胯,需要把應(yīng)用程序按照 4KB 的頁(yè)來(lái)進(jìn)行切分火诸,以頁(yè)(page)為單位放到磁盤(pán)中,然后進(jìn)行置換荠察。
為了實(shí)現(xiàn)內(nèi)存功能置蜀,Windows 在磁盤(pán)上提供了虛擬內(nèi)存使用的文件(page file奈搜,頁(yè)文件)。該文件由 Windows 生成和管理盯荤,文件的大小和虛擬內(nèi)存大小相同馋吗,通常大小是內(nèi)存的 1 - 2 倍。
磁盤(pán)的物理結(jié)構(gòu)
之前我們介紹了CPU秋秤、內(nèi)存的物理結(jié)構(gòu)宏粤,現(xiàn)在我們來(lái)介紹一下磁盤(pán)的物理結(jié)構(gòu)。磁盤(pán)的物理結(jié)構(gòu)指的是磁盤(pán)存儲(chǔ)數(shù)據(jù)的形式灼卢。
磁盤(pán)是通過(guò)其物理表面劃分成多個(gè)空間來(lái)使用的绍哎。劃分的方式有兩種:可變長(zhǎng)方式
和 扇區(qū)方式
。前者是將物理結(jié)構(gòu)劃分成長(zhǎng)度可變的空間鞋真,后者是將磁盤(pán)結(jié)構(gòu)劃分為固定長(zhǎng)度的空間崇堰。一般 Windows 所使用的硬盤(pán)和軟盤(pán)都是使用扇區(qū)這種方式。扇區(qū)中灿巧,把磁盤(pán)表面分成若干個(gè)同心圓的空間就是 磁道
赶袄,把磁道按照固定大小的存儲(chǔ)空間劃分而成的就是 扇區(qū)
扇區(qū)
是對(duì)磁盤(pán)進(jìn)行物理讀寫(xiě)的最小單位。Windows 中使用的磁盤(pán)抠藕,一般是一個(gè)扇區(qū) 512 個(gè)字節(jié)饿肺。不過(guò),Windows 在邏輯方面對(duì)磁盤(pán)進(jìn)行讀寫(xiě)的單位是扇區(qū)整數(shù)倍簇盾似。根據(jù)磁盤(pán)容量不同功能敬辣,1簇可以是 512 字節(jié)(1 簇 = 1扇區(qū))、1KB(1簇 = 2扇區(qū))零院、2KB溉跃、4KB、8KB告抄、16KB撰茎、32KB( 1 簇 = 64 扇區(qū))。簇和扇區(qū)的大小是相等的打洼。
壓縮算法
我們想必都有過(guò)壓縮
和 解壓縮
文件的經(jīng)歷龄糊,當(dāng)文件太大時(shí),我們會(huì)使用文件壓縮來(lái)降低文件的占用空間募疮。比如微信上傳文件的限制是100 MB炫惩,我這里有個(gè)文件夾無(wú)法上傳,但是我解壓完成后的文件一定會(huì)小于 100 MB阿浓,那么我的文件就可以上傳了他嚷。
此外,我們把相機(jī)拍完的照片保存到計(jì)算機(jī)上的時(shí)候,也會(huì)使用壓縮算法進(jìn)行文件壓縮筋蓖,文件壓縮的格式一般是JPEG
卸耘。
那么什么是壓縮算法呢?壓縮算法又是怎么定義的呢扭勉?在認(rèn)識(shí)算法之前我們需要先了解一下文件是如何存儲(chǔ)的
文件存儲(chǔ)
文件是將數(shù)據(jù)存儲(chǔ)在磁盤(pán)等存儲(chǔ)媒介的一種形式鹊奖。程序文件中最基本的存儲(chǔ)數(shù)據(jù)單位是字節(jié)
。文件的大小不管是 xxxKB涂炎、xxxMB等來(lái)表示忠聚,就是因?yàn)槲募且宰止?jié) B = Byte
為單位來(lái)存儲(chǔ)的。
文件就是字節(jié)數(shù)據(jù)的集合唱捣。用 1 字節(jié)(8 位)表示的字節(jié)數(shù)據(jù)有 256 種两蟀,用二進(jìn)制表示的話(huà)就是 0000 0000 - 1111 1111 。如果文件中存儲(chǔ)的數(shù)據(jù)是文字震缭,那么該文件就是文本文件赂毯。如果是圖形,那么該文件就是圖像文件拣宰。在任何情況下党涕,文件中的字節(jié)數(shù)都是連續(xù)存儲(chǔ)
的。
壓縮算法的定義
上面介紹了文件的集合體其實(shí)就是一堆字節(jié)數(shù)據(jù)的集合巡社,那么我們就可以來(lái)給壓縮算法下一個(gè)定義膛堤。
壓縮算法(compaction algorithm)
指的就是數(shù)據(jù)壓縮的算法,主要包括壓縮和還原(解壓縮)的兩個(gè)步驟晌该。
其實(shí)就是在不改變?cè)形募傩缘那疤嵯路世螅档臀募止?jié)空間和占用空間的一種算法。
根據(jù)壓縮算法的定義朝群,我們可將其分成不同的類(lèi)型:
有損和無(wú)損
無(wú)損壓縮:能夠無(wú)失真地
從壓縮后的數(shù)據(jù)重構(gòu)燕耿,準(zhǔn)確地還原原始數(shù)據(jù)〗郑可用于對(duì)數(shù)據(jù)的準(zhǔn)確性要求嚴(yán)格的場(chǎng)合誉帅,如可執(zhí)行文件和普通文件的壓縮、磁盤(pán)的壓縮右莱,也可用于多媒體數(shù)據(jù)的壓縮蚜锨。該方法的壓縮比較小。如差分編碼隧出、RLE、Huffman編碼阀捅、LZW編碼胀瞪、算術(shù)編碼。
有損壓縮:有失真,不能完全準(zhǔn)確地
恢復(fù)原始數(shù)據(jù)凄诞,重構(gòu)的數(shù)據(jù)只是原始數(shù)據(jù)的一個(gè)近似圆雁。可用于對(duì)數(shù)據(jù)的準(zhǔn)確性要求不高的場(chǎng)合帆谍,如多媒體數(shù)據(jù)的壓縮伪朽。該方法的壓縮比較大。例如預(yù)測(cè)編碼汛蝙、音感編碼烈涮、分形壓縮、小波壓縮窖剑、JPEG/MPEG坚洽。
對(duì)稱(chēng)性
如果編解碼算法的復(fù)雜性和所需時(shí)間差不多,則為對(duì)稱(chēng)的編碼方法西土,多數(shù)壓縮算法都是對(duì)稱(chēng)的讶舰。但也有不對(duì)稱(chēng)的,一般是編碼難而解碼容易需了,如 Huffman 編碼和分形編碼跳昼。但用于密碼學(xué)的編碼方法則相反,是編碼容易肋乍,而解碼則非常難鹅颊。
幀間與幀內(nèi)
在視頻編碼中會(huì)同時(shí)用到幀內(nèi)與幀間的編碼方法,幀內(nèi)編碼是指在一幀圖像內(nèi)獨(dú)立完成的編碼方法住拭,同靜態(tài)圖像的編碼挪略,如 JPEG;而幀間編碼則需要參照前后幀才能進(jìn)行編解碼滔岳,并在編碼過(guò)程中考慮對(duì)幀之間的時(shí)間冗余的壓縮杠娱,如 MPEG。
實(shí)時(shí)性
在有些多媒體的應(yīng)用場(chǎng)合谱煤,需要實(shí)時(shí)處理或傳輸數(shù)據(jù)(如現(xiàn)場(chǎng)的數(shù)字錄音和錄影摊求、播放MP3/RM/VCD/DVD、視頻/音頻點(diǎn)播刘离、網(wǎng)絡(luò)現(xiàn)場(chǎng)直播室叉、可視電話(huà)、視頻會(huì)議)硫惕,編解碼一般要求延時(shí) ≤50 ms茧痕。這就需要簡(jiǎn)單/快速/高效的算法和高速/復(fù)雜的CPU/DSP芯片。
分級(jí)處理
有些壓縮算法可以同時(shí)處理不同分辨率恼除、不同傳輸速率踪旷、不同質(zhì)量水平的多媒體數(shù)據(jù)曼氛,如JPEG2000、MPEG-2/4令野。
這些概念有些抽象舀患,主要是為了讓大家了解一下壓縮算法的分類(lèi),下面我們就對(duì)具體的幾種常用的壓縮算法來(lái)分析一下它的特點(diǎn)和優(yōu)劣
幾種常用壓縮算法的理解
RLE 算法的機(jī)制
接下來(lái)就讓我們正式看一下文件的壓縮機(jī)制气破。首先讓我們來(lái)嘗試對(duì) AAAAAABBCDDEEEEEF
這 17 個(gè)半角字符的文件(文本文件)進(jìn)行壓縮聊浅。雖然這些文字沒(méi)有什么實(shí)際意義,但是很適合用來(lái)描述 RLE
的壓縮機(jī)制现使。
由于半角字符(其實(shí)就是英文字符)是作為 1 個(gè)字節(jié)保存在文件中的低匙,所以上述的文件的大小就是 17 字節(jié)。如圖
那么朴下,如何才能壓縮該文件呢努咐?大家不妨也考慮一下,只要是能夠使文件小于 17 字節(jié)殴胧,我們可以使用任何壓縮算法渗稍。
最顯而易見(jiàn)的一種壓縮方式我覺(jué)得你已經(jīng)想到了,就是把相同的字符去重化
团滥,也就是 字符 * 重復(fù)次數(shù)
的方式進(jìn)行壓縮竿屹。所以上面文件壓縮后就會(huì)變成下面這樣
從圖中我們可以看出,AAAAAABBCDDEEEEEF 的17個(gè)字符成功被壓縮成了 A6B2C1D2E5F1 的12個(gè)字符灸姊,也就是 12 / 17 = 70%拱燃,壓縮比為 70%,壓縮成功了力惯。
像這樣碗誉,把文件內(nèi)容用 數(shù)據(jù) * 重復(fù)次數(shù)
的形式來(lái)表示的壓縮方法成為 RLE(Run Length Encoding, 行程長(zhǎng)度編碼)
算法。RLE 算法是一種很好的壓縮方法父晶,經(jīng)常用于壓縮傳真的圖像等哮缺。因?yàn)閳D像文件的本質(zhì)也是字節(jié)數(shù)據(jù)的集合體,所以可以用 RLE 算法進(jìn)行壓縮
哈夫曼算法和莫爾斯編碼
下面我們來(lái)介紹另外一種壓縮算法甲喝,即哈夫曼算法尝苇。在了解哈夫曼算法之前,你必須舍棄半角英文數(shù)字的1個(gè)字符是1個(gè)字節(jié)(8位)的數(shù)據(jù)
埠胖。下面我們就來(lái)認(rèn)識(shí)一下哈夫曼算法的基本思想糠溜。
文本文件是由不同類(lèi)型的字符組合而成的,而且不同字符出現(xiàn)的次數(shù)也是不一樣的直撤。例如非竿,在某個(gè)文本文件中,A 出現(xiàn)了 100次左右谋竖,Q僅僅用到了 3 次红柱,類(lèi)似這樣的情況很常見(jiàn)侮东。哈夫曼算法的關(guān)鍵就在于 多次出現(xiàn)的數(shù)據(jù)用小于 8 位的字節(jié)數(shù)表示,不常用的數(shù)據(jù)則可以使用超過(guò) 8 位的字節(jié)數(shù)表示豹芯。A 和 Q 都用 8 位來(lái)表示時(shí),原文件的大小就是 100次 * 8 位 + 3次 * 8 位 = 824位驱敲,假設(shè) A 用 2 位铁蹈,Q 用 10 位來(lái)表示就是 2 * 100 + 3 * 10 = 230 位。
不過(guò)要注意一點(diǎn)众眨,最終磁盤(pán)的存儲(chǔ)都是以8位為一個(gè)字節(jié)來(lái)保存文件的握牧。
哈夫曼算法比較復(fù)雜,在深入了解之前我們先吃點(diǎn)甜品
娩梨,了解一下 莫爾斯編碼
沿腰,你一定看過(guò)美劇或者戰(zhàn)爭(zhēng)片的電影,在戰(zhàn)爭(zhēng)中的通信經(jīng)常采用莫爾斯編碼來(lái)傳遞信息狈定,例如下面
接下來(lái)我們來(lái)講解一下莫爾斯編碼颂龙,下面是莫爾斯編碼的示例
,大家把 1 看作是短點(diǎn)(嘀)纽什,把 11 看作是長(zhǎng)點(diǎn)(嗒)即可措嵌。
莫爾斯編碼一般把文本中出現(xiàn)最高頻率的字符用短編碼
來(lái)表示。如表所示芦缰,假如表示短點(diǎn)的位是 1企巢,表示長(zhǎng)點(diǎn)的位是 11 的話(huà),那么 E(嘀)這一數(shù)據(jù)的字符就可以用 1 來(lái)表示让蕾,C(滴答滴答)就可以用 9 位的 110101101
來(lái)表示浪规。在實(shí)際的莫爾斯編碼中,如果短點(diǎn)的長(zhǎng)度是 1 探孝,長(zhǎng)點(diǎn)的長(zhǎng)度就是 3笋婿,短點(diǎn)和長(zhǎng)點(diǎn)的間隔就是1。這里的長(zhǎng)度指的就是聲音的長(zhǎng)度再姑。比如我們想用上面的 AAAAAABBCDDEEEEEF 例子來(lái)用莫爾斯編碼重寫(xiě)萌抵,在莫爾斯曼編碼中,各個(gè)字符之間需要加入表示時(shí)間間隔的符號(hào)元镀。這里我們用 00 加以區(qū)分绍填。
所以,AAAAAABBCDDEEEEEF 這個(gè)文本就變?yōu)榱?A * 6 次 + B * 2次 + C * 1次 + D * 2次 + E * 5次 + F * 1次 + 字符間隔 * 16 = 4 位 * 6次 + 8 位 * 2次 + 9 位 * 1 次 + 6位 * 2次 + 1位 * 5次 + 8 位 * 1次 + 2位 * 16次 = 106位 = 14字節(jié)栖疑。
所以使用莫爾斯電碼的壓縮比為 14 / 17 = 82%讨永。效率并不太突出。
用二叉樹(shù)實(shí)現(xiàn)哈夫曼算法
剛才已經(jīng)提到遇革,莫爾斯編碼是根據(jù)日常文本中各字符的出現(xiàn)頻率來(lái)決定表示各字符的編碼數(shù)據(jù)長(zhǎng)度的卿闹。不過(guò)揭糕,在該編碼體系中,對(duì) AAAAAABBCDDEEEEEF 這種文本來(lái)說(shuō)并不是效率最高的锻霎。
下面我們來(lái)看一下哈夫曼算法著角。哈夫曼算法是指,為各壓縮對(duì)象文件分別構(gòu)造最佳的編碼體系旋恼,并以該編碼體系為基礎(chǔ)來(lái)進(jìn)行壓縮吏口。因此悲靴,用什么樣的編碼(哈夫曼編碼)對(duì)數(shù)據(jù)進(jìn)行分割悦冀,就要由各個(gè)文件而定艘款。用哈夫曼算法壓縮過(guò)的文件中蝠筑,存儲(chǔ)著哈夫曼編碼信息和壓縮過(guò)的數(shù)據(jù)积锅。
接下來(lái)昼扛,我們?cè)趯?duì) AAAAAABBCDDEEEEEF 中的 A - F 這些字符辜伟,按照出現(xiàn)頻率高的字符用盡量少的位數(shù)編碼來(lái)表示
這一原則進(jìn)行整理仇穗。按照出現(xiàn)頻率從高到低的順序整理后奠衔,結(jié)果如下谆刨,同時(shí)也列出了編碼方案。
字符 | 出現(xiàn)頻率 | 編碼(方案) | 位數(shù) |
---|---|---|---|
A | 6 | 0 | 1 |
E | 5 | 1 | 1 |
B | 2 | 10 | 2 |
D | 2 | 11 | 2 |
C | 1 | 100 | 3 |
F | 1 | 101 | 3 |
在上表的編碼方案中归斤,隨著出現(xiàn)頻率的降低痴荐,字符編碼信息的數(shù)據(jù)位數(shù)也在逐漸增加,從最開(kāi)始的 1位官册、2位依次增加到3位生兆。不過(guò)這個(gè)編碼體系是存在問(wèn)題的,你不知道100這個(gè)3位的編碼膝宁,它的意思是用 1鸦难、0、0這三個(gè)編碼來(lái)表示 E员淫、A合蔽、A 呢?還是用10介返、0來(lái)表示 B拴事、A 呢?還是用100來(lái)表示 C 呢圣蝎。
而在哈夫曼算法中刃宵,通過(guò)借助哈夫曼樹(shù)的構(gòu)造編碼體系,即使在不使用字符區(qū)分符號(hào)的情況下徘公,也可以構(gòu)建能夠明確進(jìn)行區(qū)分的編碼體系牲证。不過(guò)哈夫曼樹(shù)的算法要比較復(fù)雜,下面是一個(gè)哈夫曼樹(shù)的構(gòu)造過(guò)程关面。
自然界樹(shù)的從根開(kāi)始生葉的坦袍,而哈夫曼樹(shù)則是葉生枝
哈夫曼樹(shù)能夠提升壓縮比率
使用哈夫曼樹(shù)之后十厢,出現(xiàn)頻率越高的數(shù)據(jù)所占用的位數(shù)越少,這也是哈夫曼樹(shù)的核心思想捂齐。通過(guò)上圖的步驟二可以看出蛮放,枝條連接數(shù)據(jù)時(shí),我們是從出現(xiàn)頻率較低的數(shù)據(jù)開(kāi)始的奠宜。這就意味著出現(xiàn)頻率低的數(shù)據(jù)到達(dá)根部的枝條也越多筛武。而枝條越多則意味著編碼的位數(shù)隨之增加。
接下來(lái)我們來(lái)看一下哈夫曼樹(shù)的壓縮比率挎塌,用上圖得到的數(shù)據(jù)表示 AAAAAABBCDDEEEEEF 為 000000000000 100100 110 101101 0101010101 111,40位 = 5 字節(jié)内边。壓縮前的數(shù)據(jù)是 17 字節(jié)榴都,壓縮后的數(shù)據(jù)竟然達(dá)到了驚人的5 字節(jié),也就是壓縮比率 = 5 / 17 = 29% 如此高的壓縮率漠其,簡(jiǎn)直是太驚艷了嘴高。
大家可以參考一下,無(wú)論哪種類(lèi)型的數(shù)據(jù)和屎,都可以用哈夫曼樹(shù)作為壓縮算法
文件類(lèi)型 | 壓縮前 | 壓縮后 | 壓縮比率 |
---|---|---|---|
文本文件 | 14862字節(jié) | 4119字節(jié) | 28% |
圖像文件 | 96062字節(jié) | 9456字節(jié) | 10% |
EXE文件 | 24576字節(jié) | 4652字節(jié) | 19% |
可逆壓縮和非可逆壓縮
最后拴驮,我們來(lái)看一下圖像文件的數(shù)據(jù)形式。圖像文件的使用目的通常是把圖像數(shù)據(jù)輸出到顯示器柴信、打印機(jī)等設(shè)備上套啤。常用的圖像格式有 : BMP
、JPEG
随常、TIFF
潜沦、GIF
格式等。
- BMP : 是使用 Windows 自帶的畫(huà)筆來(lái)做成的一種圖像形式
- JPEG:是數(shù)碼相機(jī)等常用的一種圖像數(shù)據(jù)形式
- TIFF: 是一種通過(guò)在文件中包含"標(biāo)簽"就能夠快速顯示出數(shù)據(jù)性質(zhì)的圖像形式
- GIF: 是由美國(guó)開(kāi)發(fā)的一種數(shù)據(jù)形式绪氛,要求色數(shù)不超過(guò) 256個(gè)
圖像文件可以使用前面介紹的 RLE 算法和哈夫曼算法唆鸡,因?yàn)閳D像文件在多數(shù)情況下并不要求數(shù)據(jù)需要還原到和壓縮之前一摸一樣的狀態(tài),允許丟失一部分?jǐn)?shù)據(jù)枣察。我們把能還原到壓縮前狀態(tài)的壓縮稱(chēng)為 可逆壓縮
争占,無(wú)法還原到壓縮前狀態(tài)的壓縮稱(chēng)為非可逆壓縮
。
一般來(lái)說(shuō)序目,JPEG格式的文件是非可逆壓縮臂痕,因此還原后有部分圖像信息比較模糊。GIF 是可逆壓縮
操作系統(tǒng)
操作系統(tǒng)環(huán)境
程序中包含著運(yùn)行環(huán)境
這一內(nèi)容猿涨,可以說(shuō) 運(yùn)行環(huán)境 = 操作系統(tǒng) + 硬件 刻蟹,操作系統(tǒng)又可以被稱(chēng)為軟件,它是由一系列的指令組成的嘿辟。我們不介紹操作系統(tǒng)舆瘪,我們主要來(lái)介紹一下硬件的識(shí)別片效。
我們肯定都玩兒過(guò)游戲,你玩兒游戲前需要干什么英古?是不是需要先看一下自己的筆記本或者電腦是不是能肝的起游戲淀衣?下面是一個(gè)游戲的配置(懷念一下 wow)
圖中的主要配置如下
操作系統(tǒng)版本:說(shuō)的就是應(yīng)用程序運(yùn)行在何種系統(tǒng)環(huán)境,現(xiàn)在市面上主要有三種操作系統(tǒng)環(huán)境召调,Windows 膨桥、Linux 和 Unix ,一般我們玩兒的大型游戲幾乎都是在 Windows 上運(yùn)行唠叛,可以說(shuō) Windows 是游戲的天堂只嚣。Windows 操作系統(tǒng)也會(huì)有區(qū)分,分為32位操作系統(tǒng)和64位操作系統(tǒng)艺沼,互不兼容册舞。
處理器:處理器指的就是 CPU,你的電腦的計(jì)算能力障般,通俗來(lái)講就是每秒鐘能處理的指令數(shù)调鲸,如果你的電腦覺(jué)得卡帶不起來(lái)的話(huà),很可能就是 CPU 的計(jì)算能力不足導(dǎo)致的挽荡。想要加深理解藐石,請(qǐng)閱讀博主的另一篇文章:程序員需要了解的硬核知識(shí)之CPU
顯卡:顯卡承擔(dān)圖形的輸出任務(wù),因此又被稱(chēng)為圖形處理器(Graphic Processing Unit定拟,GPU)于微,顯卡也非常重要,比如我之前玩兒的
劍靈
開(kāi)五檔(其實(shí)就是圖像變得更清晰)會(huì)卡青自,其實(shí)就是顯卡顯示不出來(lái)的原因角雷。內(nèi)存:內(nèi)存即主存,就是你的應(yīng)用程序在運(yùn)行時(shí)能夠動(dòng)態(tài)分析指令的這部分存儲(chǔ)空間性穿,它的大小也能決定你電腦的運(yùn)行速度勺三,想要加深理解,請(qǐng)閱讀博主的另一篇文章 程序員需要了解的硬核知識(shí)之內(nèi)存
存儲(chǔ)空間:存儲(chǔ)空間指的就是應(yīng)用程序安裝所占用的磁盤(pán)空間需曾,由圖中可知吗坚,此游戲的最低存儲(chǔ)空間必須要大于 5GB,其實(shí)我們都會(huì)遺留很大一部分用來(lái)安裝游戲呆万。
從程序的運(yùn)行環(huán)境這一角度來(lái)考量的話(huà)商源,CPU 的種類(lèi)是特別重要的參數(shù),為了使程序能夠正常運(yùn)行谋减,必須滿(mǎn)足 CPU 所需的最低配置牡彻。
CPU 只能解釋其自身固有的語(yǔ)言。不同的 CPU 能解釋的機(jī)器語(yǔ)言的種類(lèi)也是不同的。機(jī)器語(yǔ)言的程序稱(chēng)為 本地代碼(native code)
庄吼,程序員用 C 等高級(jí)語(yǔ)言編寫(xiě)的程序缎除,僅僅是文本文件。文本文件(排除文字編碼的問(wèn)題)
在任何環(huán)境下都能顯示和編輯总寻。我們稱(chēng)之為源代碼
器罐。通過(guò)對(duì)源代碼進(jìn)行編譯,就可以得到本地代碼
渐行。下圖反映了這個(gè)過(guò)程轰坊。
uploading-image-703074.png
Windows 操作系統(tǒng)克服了CPU以外的硬件差異
計(jì)算機(jī)的硬件并不僅僅是由 CPU 組成的,還包括用于存儲(chǔ)程序指令的數(shù)據(jù)和內(nèi)存祟印,以及通過(guò) I/O 連接的鍵盤(pán)肴沫、顯示器、硬盤(pán)蕴忆、打印機(jī)等外圍設(shè)備颤芬。
在 WIndows 軟件中,鍵盤(pán)輸入孽文、顯示器輸出等并不是直接向硬件發(fā)送指令。而是通過(guò)向 Windows 發(fā)送指令實(shí)現(xiàn)的夺艰。因此芋哭,程序員就不用注意內(nèi)存和 I/O 地址的不同構(gòu)成了。Windows 操作的是硬件而不是軟件郁副,軟件通過(guò)操作 Windows 系統(tǒng)可以達(dá)到控制硬件的目的减牺。
不同操作系統(tǒng)的 API 差異性
接下來(lái)我們看一下操作系統(tǒng)的種類(lèi)。同樣機(jī)型的計(jì)算機(jī)存谎,可安裝的操作系統(tǒng)類(lèi)型也會(huì)有多種選擇拔疚。例如:AT 兼容機(jī)除了可以安裝 Windows 之外,還可以采用 Unix 系列的 Linux 以及 FreeBSD (也是一種Unix操作系統(tǒng))等多個(gè)操作系統(tǒng)既荚。當(dāng)然稚失,應(yīng)用軟件則必須根據(jù)不同的操作系統(tǒng)類(lèi)型來(lái)專(zhuān)門(mén)開(kāi)發(fā)。CPU 的類(lèi)型不同恰聘,所對(duì)應(yīng)機(jī)器的語(yǔ)言也不同,同樣的道理句各,操作系統(tǒng)的類(lèi)型不同,應(yīng)用程序向操作系統(tǒng)傳遞指令的途徑也不同晴叨。
應(yīng)用程序向系統(tǒng)傳遞指令的途徑稱(chēng)為 API(Application Programming Interface)
凿宾。Windows 以及 Linux 操作系統(tǒng)的 API,提供了任何應(yīng)用程序都可以利用的函數(shù)組合兼蕊。因?yàn)椴煌僮飨到y(tǒng)的 API 是有差異的初厚。所以,如何要將同樣的應(yīng)用程序移植到另外的操作系統(tǒng)孙技,就必須要覆蓋應(yīng)用所用到的 API 部分产禾。
鍵盤(pán)輸入排作、鼠標(biāo)輸入、顯示器輸出下愈、文件輸入和輸出等同外圍設(shè)備進(jìn)行交互的功能纽绍,都是通過(guò) API 提供的。
這也就是為什么 Windows 應(yīng)用程序不能直接移植到 Linux 操作系統(tǒng)上的原因势似,API 差異太大了拌夏。
在同類(lèi)型的操作系統(tǒng)下,不論硬件如何履因,API 幾乎相同障簿。但是,由于不同種類(lèi) CPU 的機(jī)器語(yǔ)言不同栅迄,因此本地代碼也不盡相同站故。
操作系統(tǒng)功能的歷史
操作系統(tǒng)
其實(shí)也是一種軟件,任何新事物的出現(xiàn)肯定都有它的歷史背景毅舆,那么操作系統(tǒng)也不是憑空出現(xiàn)的西篓,肯定有它的歷史背景。
在計(jì)算機(jī)尚不存在操作系統(tǒng)的年代憋活,完全沒(méi)有任何程序岂津,人們通過(guò)各種按鈕
來(lái)控制計(jì)算機(jī),這一過(guò)程非常麻煩悦即。于是吮成,有人開(kāi)發(fā)出了僅具有加載和運(yùn)行功能的監(jiān)控程序
,這就是操作系統(tǒng)的原型辜梳。通過(guò)事先啟動(dòng)監(jiān)控程序粱甫,程序員可以根據(jù)需要將各種程序加載到內(nèi)存中運(yùn)行。雖然仍舊比較麻煩作瞄,但比起在沒(méi)有任何程序的狀態(tài)下進(jìn)行開(kāi)發(fā)茶宵,工作量得到了很大的緩解。
隨著時(shí)代的發(fā)展宗挥,人們?cè)诶帽O(jiān)控程序編寫(xiě)程序的過(guò)程中發(fā)現(xiàn)很多程序都有公共的部分节预。例如,通過(guò)鍵盤(pán)進(jìn)行文字輸入属韧,顯示器進(jìn)行數(shù)據(jù)展示等安拟,如果每編寫(xiě)一個(gè)新的應(yīng)用程序都需要相同的處理的話(huà),那真是太浪費(fèi)時(shí)間了宵喂。因此糠赦,基本的輸入輸出部分的程序就被追加到了監(jiān)控程序中。初期的操作系統(tǒng)就是這樣誕生了。
類(lèi)似的想法可以共用拙泽,人們又發(fā)現(xiàn)有更多的應(yīng)用程序可以追加到監(jiān)控程序中淌山,比如硬件控制程序
,編程語(yǔ)言處理器(匯編顾瞻、編譯泼疑、解析)
以及各種應(yīng)用程序等,結(jié)果就形成了和現(xiàn)在差異不大的操作系統(tǒng)荷荤,也就是說(shuō)退渗,其實(shí)操作系統(tǒng)是多個(gè)程序的集合體。
Windows 操作系統(tǒng)的特征
Windows 操作系統(tǒng)是世界上用戶(hù)數(shù)量最龐大的群體蕴纳,作為 Windows 操作系統(tǒng)的資深
用戶(hù)会油,你都知道 Windows 操作系統(tǒng)有哪些特征嗎?下面列舉了一些 Windows 操作系統(tǒng)的特性
- Windows 操作系統(tǒng)有兩個(gè)版本:32位和64位
- 通過(guò)
API
函數(shù)集成來(lái)提供系統(tǒng)調(diào)用 - 提供了采用圖形用戶(hù)界面的用戶(hù)界面
- 通過(guò)
WYSIWYG
實(shí)現(xiàn)打印輸出古毛,WYSIWYG 其實(shí)就是 What You See Is What You Get 翻翩,值得是顯示器上顯示的圖形和文本都是可以原樣輸出到打印機(jī)打印的。 - 提供多任務(wù)功能稻薇,即能夠同時(shí)開(kāi)啟多個(gè)任務(wù)
- 提供網(wǎng)絡(luò)功能和數(shù)據(jù)庫(kù)功能
- 通過(guò)即插即用實(shí)現(xiàn)設(shè)備驅(qū)動(dòng)的自設(shè)定
這些是對(duì)程序員來(lái)講比較有意義的一些特征嫂冻,下面針對(duì)這些特征來(lái)進(jìn)行分別的介紹
32位操作系統(tǒng)
這里表示的32位操作系統(tǒng)表示的是處理效率最高的數(shù)據(jù)大小。Windows 處理數(shù)據(jù)的基本單位是 32 位塞椎。這與最一開(kāi)始在 MS-DOS
等16位操作系統(tǒng)不同桨仿,因?yàn)樵?6位操作系統(tǒng)中處理32位數(shù)據(jù)需要兩次,而32位操作系統(tǒng)只需要一次就能夠處理32位的數(shù)據(jù)忱屑,所以一般在 windows 上的應(yīng)用蹬敲,它們的最高能夠處理的數(shù)據(jù)都是 32 位的暇昂。
比如莺戒,用 C 語(yǔ)言來(lái)處理整數(shù)數(shù)據(jù)時(shí),有8位的 char
類(lèi)型急波,16位的short
類(lèi)型从铲,以及32位的long
類(lèi)型三個(gè)選項(xiàng),使用位數(shù)較大的 long 類(lèi)型進(jìn)行處理的話(huà)澄暮,增加的只是內(nèi)存以及磁盤(pán)的開(kāi)銷(xiāo)名段,對(duì)性能影響不大。
現(xiàn)在市面上大部分都是64位操作系統(tǒng)了泣懊,64位操作系統(tǒng)也是如此伸辟。
通過(guò) API 函數(shù)集來(lái)提供系統(tǒng)調(diào)用
Windows 是通過(guò)名為 API
的函數(shù)集來(lái)提供系統(tǒng)調(diào)用的。API是聯(lián)系應(yīng)用程序和操作系統(tǒng)之間的接口馍刮,全稱(chēng)叫做 Application Programming Interface
,應(yīng)用程序接口信夫。
當(dāng)前主流的32位版 Windows API 也稱(chēng)為 Win32 API
,之所以這樣命名,是需要和不同的操作系統(tǒng)進(jìn)行區(qū)分静稻,比如最一開(kāi)始的 16 位版的 Win16 API
警没,和后來(lái)流行的 Win64 API
。
API 通過(guò)多個(gè) DLL 文件來(lái)提供振湾,各個(gè) API 的實(shí)體都是用 C 語(yǔ)言編寫(xiě)的函數(shù)杀迹。所以,在 C 語(yǔ)言環(huán)境下押搪,使用 API 更加容易树酪,比如 API 所用到的 MessageBox()
函數(shù),就被保存在了 Windows 提供的 user32.dll 這個(gè) DLL 文件中嵌言。
提供采用了 GUI 的用戶(hù)界面
GUI(Graphical User Interface)
指得就是圖形用戶(hù)界面嗅回,通過(guò)點(diǎn)擊顯示器中的窗口以及圖標(biāo)等可視化的用戶(hù)界面,舉個(gè)例子:Linux 操作系統(tǒng)就有兩個(gè)版本摧茴,一種是簡(jiǎn)潔版绵载,直接通過(guò)命令行控制硬件,還有一種是可視化版苛白,通過(guò)光標(biāo)點(diǎn)擊圖形界面來(lái)控制硬件娃豹。
通過(guò) WYSIWYG 實(shí)現(xiàn)打印輸出
WYSIWYG 指的是顯示器上輸出的內(nèi)容可以直接通過(guò)打印機(jī)打印輸出。在 Windows 中购裙,顯示器和打印機(jī)被認(rèn)作同等的圖形輸出設(shè)備處理的懂版,該功能也為 WYSIWYG 提供了條件。
借助 WYSIWYG 功能躏率,程序員可以輕松不少躯畴。最初,為了是現(xiàn)在顯示器中顯示和在打印機(jī)中打印薇芝,就必須分別編寫(xiě)各自的程序蓬抄,而在 Windows 中浩淘,可以借助 WYSIWYG 基本上在一個(gè)程序中就可以做到顯示和打印這兩個(gè)功能了首启。
提供多任務(wù)功能
多任務(wù)指的就是同時(shí)能夠運(yùn)行多個(gè)應(yīng)用程序的功能,Windows 是通過(guò)時(shí)鐘分割
技術(shù)來(lái)實(shí)現(xiàn)多任務(wù)功能的看杭。時(shí)鐘分割指的是短時(shí)間間隔內(nèi)耍贾,多個(gè)程序切換運(yùn)行的方式阅爽。在用戶(hù)看來(lái),就好像是多個(gè)程序在同時(shí)運(yùn)行荐开,其底層是 CPU 時(shí)間切片
付翁,這也是多線(xiàn)程多任務(wù)的核心。
提供網(wǎng)絡(luò)功能和數(shù)據(jù)庫(kù)功能
Windows 中晃听,網(wǎng)絡(luò)功能是作為標(biāo)準(zhǔn)功能提供的百侧。數(shù)據(jù)庫(kù)(數(shù)據(jù)庫(kù)服務(wù)器)功能有時(shí)也會(huì)在后面追加着帽。網(wǎng)絡(luò)功能和數(shù)據(jù)庫(kù)功能雖然并不是操作系統(tǒng)不可或缺的,但因?yàn)樗鼈兒筒僮飨到y(tǒng)很接近移层,所以被統(tǒng)稱(chēng)為中間件
而不是應(yīng)用仍翰。意思是處于操作系統(tǒng)和應(yīng)用的中間層,操作系統(tǒng)和中間件組合在一起观话,稱(chēng)為系統(tǒng)軟件
予借。應(yīng)用不僅可以利用操作系統(tǒng),也可以利用中間件的功能频蛔。
相對(duì)于操作系統(tǒng)一旦安裝就不能輕易更換灵迫,中間件可以根據(jù)需要進(jìn)行更換,不過(guò)晦溪,對(duì)于大部分應(yīng)用來(lái)說(shuō)瀑粥,更換中間件的話(huà),會(huì)造成應(yīng)用也隨之更換三圆,從這個(gè)角度來(lái)說(shuō)狞换,更?換中間件也不是那么容易。
通過(guò)即插即用實(shí)現(xiàn)設(shè)備驅(qū)動(dòng)的自動(dòng)設(shè)定
即插即用(Plug-and-Play)
指的是新的設(shè)備連接(plug) 后就可以直接使用的機(jī)制舟肉,新設(shè)備連接計(jì)算機(jī)后修噪,計(jì)算機(jī)就會(huì)自動(dòng)安裝和設(shè)定用來(lái)控制該設(shè)備的驅(qū)動(dòng)程序
設(shè)備驅(qū)動(dòng)是操作系統(tǒng)的一部分,提供了同硬件進(jìn)行基本的輸入輸出的功能路媚。鍵盤(pán)黄琼、鼠標(biāo)、顯示器整慎、磁盤(pán)裝置等脏款,這些計(jì)算機(jī)中必備的硬件的設(shè)備驅(qū)動(dòng),一般都是隨操作系統(tǒng)一起安裝的裤园。
有時(shí) DLL 文件也會(huì)同設(shè)備驅(qū)動(dòng)文件一起安裝撤师。這些 DLL 文件中存儲(chǔ)著用來(lái)利用該新追加的硬件API,通過(guò) API 比然,可以制作出運(yùn)行該硬件的心應(yīng)用丈氓。
匯編語(yǔ)言和本地代碼
我們?cè)谥暗奈恼轮刑接戇^(guò)周循,計(jì)算機(jī) CPU 只能運(yùn)行本地代碼(機(jī)器語(yǔ)言)程序强法,用 C 語(yǔ)言等高級(jí)語(yǔ)言編寫(xiě)的代碼,需要經(jīng)過(guò)編譯器編譯后湾笛,轉(zhuǎn)換為本地代碼才能夠被 CPU 解釋執(zhí)行饮怯。
但是本地代碼的可讀性非常差,所以需要使用一種能夠直接讀懂的語(yǔ)言來(lái)替換本地代碼嚎研,那就是在各本地代碼中蓖墅,附帶上表示其功能的英文縮寫(xiě)库倘,比如在加法運(yùn)算的本地代碼加上add(addition)
的縮寫(xiě)、在比較運(yùn)算符的本地代碼中加上cmp(compare)
的縮寫(xiě)等论矾,這些通過(guò)縮寫(xiě)來(lái)表示具體本地代碼指令的標(biāo)志稱(chēng)為 助記符
教翩,使用助記符的語(yǔ)言稱(chēng)為匯編語(yǔ)言
。這樣贪壳,通過(guò)閱讀匯編語(yǔ)言彪笼,也能夠了解本地代碼的含義了配猫。
不過(guò)泵肄,即使是使用匯編語(yǔ)言編寫(xiě)的源代碼淑翼,最終也必須要轉(zhuǎn)換為本地代碼才能夠運(yùn)行凡伊,負(fù)責(zé)做這項(xiàng)工作的程序稱(chēng)為編譯器
,轉(zhuǎn)換的這個(gè)過(guò)程稱(chēng)為匯編
窒舟。在將源代碼轉(zhuǎn)換為本地代碼這個(gè)功能方面系忙,匯編器和編譯器是同樣的。
用匯編語(yǔ)言編寫(xiě)的源代碼和本地代碼是一一對(duì)應(yīng)的惠豺。因而银还,本地代碼也可以反過(guò)來(lái)轉(zhuǎn)換成匯編語(yǔ)言編寫(xiě)的代碼。把本地代碼轉(zhuǎn)換為匯編代碼的這一過(guò)程稱(chēng)為反匯編
洁墙,執(zhí)行反匯編的程序稱(chēng)為反匯編程序
蛹疯。
哪怕是 C 語(yǔ)言編寫(xiě)的源代碼,編譯后也會(huì)轉(zhuǎn)換成特定 CPU 用的本地代碼热监。而將其反匯編的話(huà)孝扛,就可以得到匯編語(yǔ)言的源代碼陌选,并對(duì)其內(nèi)容進(jìn)行調(diào)查。不過(guò)餐弱,本地代碼變成 C 語(yǔ)言源代碼的反編譯驮瞧,要比本地代碼轉(zhuǎn)換成匯編代碼的反匯編要困難狂魔,這是因?yàn)樽阉铮珻 語(yǔ)言代碼和本地代碼不是一一對(duì)應(yīng)的關(guān)系适瓦。
通過(guò)編譯器輸出匯編語(yǔ)言的源代碼
我們上面提到本地代碼可以經(jīng)過(guò)反匯編轉(zhuǎn)換成為匯編代碼揭芍,但是只有這一種轉(zhuǎn)換方式嗎筷转?顯然不是袭蝗,C 語(yǔ)言編寫(xiě)的源代碼也能夠通過(guò)編譯器編譯稱(chēng)為匯編代碼乡范,下面就來(lái)嘗試一下瓶佳。
首先需要先做一些準(zhǔn)備贴彼,需要先下載 Borland C++ 5.5
編譯器,為了方便,我這邊直接下載好了讀者直接從我的百度網(wǎng)盤(pán)提取即可 (鏈接:pan.baidu.com/s/19LqVICpn… 密碼:hz1u)
下載完畢,需要進(jìn)行配置根蟹,下面是配置說(shuō)明 (wenku.baidu.com/view/22e2f4…
首先用 Windows 記事本等文本編輯器編寫(xiě)如下代碼
// 返回兩個(gè)參數(shù)值之和的函數(shù)
int AddNum(int a,int b){
return a + b;
}
// 調(diào)用 AddNum 函數(shù)的函數(shù)
void MyFunc(){
int c;
c = AddNum(123,456);
}
編寫(xiě)完成后將其文件名保存為 Sample4.c 散庶,C 語(yǔ)言源文件的擴(kuò)展名冰寻,通常用.c
來(lái)表示,上面程序是提供兩個(gè)輸入?yún)?shù)并返回它們之和迁筛。
在 Windows 操作系統(tǒng)下打開(kāi) 命令提示符
,切換到保存 Sample4.c 的文件夾下,然后在命令提示符中輸入
bcc32 -c -S Sample4.c
bcc32 是啟動(dòng) Borland C++ 的命令,-c
的選項(xiàng)是指僅進(jìn)行編譯而不進(jìn)行鏈接撩匕,-S
選項(xiàng)被用來(lái)指定生成匯編語(yǔ)言的源代碼
作為編譯的結(jié)果,當(dāng)前目錄下會(huì)生成一個(gè)名為Sample4.asm
的匯編語(yǔ)言源代碼令漂。匯編語(yǔ)言源文件的擴(kuò)展名,通常用.asm
來(lái)表示,下面就讓我們用編輯器打開(kāi)看一下 Sample4.asm 中的內(nèi)容
.386p
ifdef ??version
if ??version GT 500H
.mmx
endif
endif
model flat
ifndef ??version
?debug macro
endm
endif
?debug S "Sample4.c"
?debug T "Sample4.c"
_TEXT segment dword public use32 'CODE'
_TEXT ends
_DATA segment dword public use32 'DATA'
_DATA ends
_BSS segment dword public use32 'BSS'
_BSS ends
DGROUP group _BSS,_DATA
_TEXT segment dword public use32 'CODE'
_AddNum proc near
?live1@0:
;
; int AddNum(int a,int b){
;
push ebp
mov ebp,esp
;
;
; return a + b;
;
@1:
mov eax,dword ptr [ebp+8]
add eax,dword ptr [ebp+12]
;
; }
;
@3:
@2:
pop ebp
ret
_AddNum endp
_MyFunc proc near
?live1@48:
;
; void MyFunc(){
;
push ebp
mov ebp,esp
;
; int c;
; c = AddNum(123,456);
;
@4:
push 456
push 123
call _AddNum
add esp,8
;
; }
;
@5:
pop ebp
ret
_MyFunc endp
_TEXT ends
public _AddNum
public _MyFunc
?debug D "Sample4.c" 20343 45835
end
這樣隅茎,編譯器就成功的把 C 語(yǔ)言轉(zhuǎn)換成為了匯編代碼了堂竟。
不會(huì)轉(zhuǎn)換成本地代碼的偽指令
第一次看到匯編代碼的讀者可能感覺(jué)起來(lái)比較難税稼,不過(guò)實(shí)際上其實(shí)比較簡(jiǎn)單丸升,而且可能比 C 語(yǔ)言還要簡(jiǎn)單岭皂,為了便于閱讀匯編代碼的源代碼土至,需要注意幾個(gè)要點(diǎn)
匯編語(yǔ)言的源代碼楷扬,是由轉(zhuǎn)換成本地代碼的指令(后面講述的操作碼)和針對(duì)匯編器的偽指令構(gòu)成的徘溢。偽指令負(fù)責(zé)把程序的構(gòu)造以及匯編的方法指示給匯編器(轉(zhuǎn)換程序)。不過(guò)偽指令是無(wú)法匯編轉(zhuǎn)換成為本地代碼的揩环。下面是上面程序截取的偽指令
_TEXT segment dword public use32 'CODE'
_TEXT ends
_DATA segment dword public use32 'DATA'
_DATA ends
_BSS segment dword public use32 'BSS'
_BSS ends
DGROUP group _BSS,_DATA
_AddNum proc near
_AddNum endp
_MyFunc proc near
_MyFunc endp
_TEXT ends
end
由偽指令 segment
和 ends
圍起來(lái)的部分擎宝,是給構(gòu)成程序的命令和數(shù)據(jù)的集合體上加一個(gè)名字而得到的,稱(chēng)為段定義
灰伟。段定義的英文表達(dá)具有區(qū)域
的意思挡爵,在這個(gè)程序中艰亮,段定義指的是命令和數(shù)據(jù)等程序的集合體的意思闭翩,一個(gè)程序由多個(gè)段定義構(gòu)成。
上面代碼的開(kāi)始位置迄埃,定義了3個(gè)名稱(chēng)分別為 _TEXT侄非、_DATA笔时、_BSS
的段定義,_TEXT
是指定的段定義涉茧,_DATA
是被初始化(有初始值)的數(shù)據(jù)的段定義,_BSS
是尚未初始化的數(shù)據(jù)的段定義。這種定義的名稱(chēng)是由 Borland C++ 定義的,是由 Borland C++ 編譯器自動(dòng)分配的,所以程序段定義的順序就成為了 _TEXT蔑鹦、_DATA当辐、_BSS
为牍,這樣也確保了內(nèi)存的連續(xù)性
_TEXT segment dword public use32 'CODE'
_TEXT ends
_DATA segment dword public use32 'DATA'
_DATA ends
_BSS segment dword public use32 'BSS'
_BSS ends
段定義( segment ) 是用來(lái)區(qū)分或者劃分范圍區(qū)域的意思。匯編語(yǔ)言的 segment 偽指令表示段定義的起始,ends 偽指令表示段定義的結(jié)束贞瞒。段定義是一段連續(xù)的內(nèi)存空間
而group
這個(gè)偽指令表示的是將 _BSS和_DATA
這兩個(gè)段定義匯總名為 DGROUP 的組
DGROUP group _BSS,_DATA
圍起 _AddNum
和 _MyFun
的 _TEXT
segment 和 _TEXT
ends 贩绕,表示_AddNum
和 _MyFun
是屬于 _TEXT
這一段定義的马澈。
_TEXT segment dword public use32 'CODE'
_TEXT ends
因此妖碉,即使在源代碼中指令和數(shù)據(jù)是混雜編寫(xiě)的涌庭,經(jīng)過(guò)編譯和匯編后,也會(huì)轉(zhuǎn)換成為規(guī)整的本地代碼欧宜。
_AddNum proc
和 _AddNum endp
圍起來(lái)的部分坐榆,以及_MyFunc proc
和 _MyFunc endp
圍起來(lái)的部分,分別表示 AddNum 函數(shù)和 MyFunc 函數(shù)的范圍鱼鸠。
_AddNum proc near
_AddNum endp
_MyFunc proc near
_MyFunc endp
編譯后在函數(shù)名前附帶上下劃線(xiàn)_
猛拴,是 Borland C++ 的規(guī)定。在 C 語(yǔ)言中編寫(xiě)的 AddNum 函數(shù)蚀狰,在內(nèi)部是以 _AddNum 這個(gè)名稱(chēng)處理的。偽指令 proc 和 endp 圍起來(lái)的部分职员,表示的是 過(guò)程(procedure)
的范圍麻蹋。在匯編語(yǔ)言中,這種相當(dāng)于 C 語(yǔ)言的函數(shù)的形式稱(chēng)為過(guò)程焊切。
末尾的 end
偽指令扮授,表示的是源代碼的結(jié)束芳室。
匯編語(yǔ)言的語(yǔ)法是 操作碼 + 操作數(shù)
在匯編語(yǔ)言中,一行表示一對(duì) CPU 的一個(gè)指令刹勃。匯編語(yǔ)言指令的語(yǔ)法結(jié)構(gòu)是 操作碼 + 操作數(shù)堪侯,也存在只有操作碼沒(méi)有操作數(shù)的指令。
操作碼表示的是指令動(dòng)作荔仁,操作數(shù)表示的是指令對(duì)象伍宦。操作碼和操作數(shù)一起使用就是一個(gè)英文指令。比如從英語(yǔ)語(yǔ)法來(lái)分析的話(huà)乏梁,操作碼是動(dòng)詞次洼,操作數(shù)是賓語(yǔ)。比如這個(gè)句子 Give me money
這個(gè)英文指令的話(huà)遇骑,Give 就是操作碼卖毁,me 和 money 就是操作數(shù)。匯編語(yǔ)言中存在多個(gè)操作數(shù)的情況落萎,要用逗號(hào)把它們分割亥啦,就像是 Give me,money 這樣。
能夠使用何種形式的操作碼练链,是由 CPU 的種類(lèi)決定的翔脱,下面對(duì)操作碼的功能進(jìn)行了整理。
本地代碼需要加載到內(nèi)存后才能運(yùn)行兑宇,內(nèi)存中存儲(chǔ)著構(gòu)成本地代碼的指令和數(shù)據(jù)碍侦。程序運(yùn)行時(shí),CPU會(huì)從內(nèi)存中把數(shù)據(jù)和指令讀出來(lái)隶糕,然后放在 CPU 內(nèi)部的寄存器中進(jìn)行處理瓷产。
如果 CPU 和內(nèi)存的關(guān)系你還不是很了解的話(huà),請(qǐng)閱讀作者的另一篇文章 程序員需要了解的硬核知識(shí)之CPU 詳細(xì)了解枚驻。
寄存器是 CPU 中的存儲(chǔ)區(qū)域濒旦,寄存器除了具有臨時(shí)存儲(chǔ)和計(jì)算的功能之外,還具有運(yùn)算功能再登,x86 系列的主要種類(lèi)和角色如下圖所示
指令解析
下面就對(duì) CPU 中的指令進(jìn)行分析
最常用的 mov 指令
指令中最常使用的是對(duì)寄存器和內(nèi)存進(jìn)行數(shù)據(jù)存儲(chǔ)的 mov
指令尔邓,mov 指令的兩個(gè)操作數(shù),分別用來(lái)指定數(shù)據(jù)的存儲(chǔ)地和讀出源锉矢。操作數(shù)中可以指定寄存器梯嗽、常數(shù)、標(biāo)簽(附加在地址前)沽损,以及用方括號(hào)([])
圍起來(lái)的這些內(nèi)容灯节。如果指定了沒(méi)有用([])
方括號(hào)圍起來(lái)的內(nèi)容,就表示對(duì)該值進(jìn)行處理;如果指定了用方括號(hào)圍起來(lái)的內(nèi)容炎疆,方括號(hào)的值則會(huì)被解釋為內(nèi)存地址卡骂,然后就會(huì)對(duì)該內(nèi)存地址對(duì)應(yīng)的值進(jìn)行讀寫(xiě)操作。讓我們對(duì)上面的代碼片段進(jìn)行說(shuō)明
mov ebp,esp
mov eax,dword ptr [ebp+8]
mov ebp,esp 中形入,esp 寄存器中的值被直接存儲(chǔ)在了 ebp 中全跨,也就是說(shuō),如果 esp 寄存器的值是100的話(huà)那么 ebp 寄存器的值也是 100亿遂。
而在 mov eax,dword ptr [ebp+8]
這條指令中浓若,ebp 寄存器的值 + 8 后會(huì)被解析稱(chēng)為內(nèi)存地址。如果 ebp
寄存器的值是100的話(huà)崩掘,那么 eax 寄存器的值就是 100 + 8 的地址的值七嫌。dword ptr
也叫做 double word pointer
簡(jiǎn)單解釋一下就是從指定的內(nèi)存地址中讀出4字節(jié)的數(shù)據(jù)
對(duì)棧進(jìn)行 push 和 pop
程序運(yùn)行時(shí),會(huì)在內(nèi)存上申請(qǐng)分配一個(gè)稱(chēng)為棧的數(shù)據(jù)空間苞慢。棧(stack)的特性是后入先出诵原,數(shù)據(jù)在存儲(chǔ)時(shí)是從內(nèi)存的下層(大的地址編號(hào))逐漸往上層(小的地址編號(hào))累積,讀出時(shí)則是按照從上往下進(jìn)行讀取的挽放。
棧是存儲(chǔ)臨時(shí)數(shù)據(jù)的區(qū)域绍赛,它的特點(diǎn)是通過(guò) push 指令和 pop 指令進(jìn)行數(shù)據(jù)的存儲(chǔ)和讀出。向棧中存儲(chǔ)數(shù)據(jù)稱(chēng)為 入棧
辑畦,從棧中讀出數(shù)據(jù)稱(chēng)為 出棧
吗蚌,32位 x86 系列的 CPU 中,進(jìn)行1次 push 或者 pop纯出,即可處理 32 位(4字節(jié))的數(shù)據(jù)蚯妇。
函數(shù)的調(diào)用機(jī)制
下面我們一起來(lái)分析一下函數(shù)的調(diào)用機(jī)制,我們以上面的 C 語(yǔ)言編寫(xiě)的代碼為例暂筝。首先箩言,讓我們從MyFunc
函數(shù)調(diào)用AddNum
函數(shù)的匯編語(yǔ)言部分開(kāi)始,來(lái)對(duì)函數(shù)的調(diào)用機(jī)制進(jìn)行說(shuō)明焕襟。棧在函數(shù)的調(diào)用中發(fā)揮了巨大的作用陨收,下面是經(jīng)過(guò)處理后的 MyFunc 函數(shù)的匯編處理內(nèi)容
_MyFunc proc near
push ebp ; 將 ebp 寄存器的值存入棧中 (1)
mov ebp,esp ; 將 esp 寄存器的值存入 ebp 寄存器中 (2)
push 456 ; 將 456 入棧 (3)
push 123 ; 將 123 入棧 (4)
call _AddNum ; 調(diào)用 AddNum 函數(shù) (5)
add esp,8 ; esp 寄存器的值 + 8 (6)
pop ebp ; 讀出棧中的數(shù)值存入 esp 寄存器中 (7)
ret ; 結(jié)束 MyFunc 函數(shù),返回到調(diào)用源 (8)
_MyFunc endp
代碼解釋中的(1)鸵赖、(2)务漩、(7)、(8)的處理適用于 C 語(yǔ)言中的所有函數(shù)它褪,我們會(huì)在后面展示 AddNum
函數(shù)處理內(nèi)容時(shí)進(jìn)行說(shuō)明饵骨。這里希望大家先關(guān)注(3) - (6) 這一部分,這對(duì)了解函數(shù)調(diào)用機(jī)制至關(guān)重要茫打。
(3) 和 (4) 表示的是將傳遞給 AddNum 函數(shù)的參數(shù)通過(guò) push 入棧宏悦。在 C 語(yǔ)言源代碼中镐确,雖然記述為函數(shù) AddNum(123,456)包吝,但入棧時(shí)則會(huì)先按照 456饼煞,123 這樣的順序。也就是位于后面的數(shù)值先入棧诗越。這是 C 語(yǔ)言的規(guī)定砖瞧。(5) 表示的 call 指令,會(huì)把程序流程跳轉(zhuǎn)到 AddNum 函數(shù)指令的地址處嚷狞。在匯編語(yǔ)言中块促,函數(shù)名
表示的就是函數(shù)所在的內(nèi)存地址。AddNum 函數(shù)處理完畢后床未,程序流程必須要返回到編號(hào)(6) 這一行竭翠。call 指令運(yùn)行后,call 指令的下一行(也就指的是 (6) 這一行)的內(nèi)存地址(調(diào)用函數(shù)完畢后要返回的內(nèi)存地址)會(huì)自動(dòng)的 push 入棧薇搁。該值會(huì)在 AddNum 函數(shù)處理的最后通過(guò) ret
指令 pop 出棧,然后程序會(huì)返回到 (6) 這一行啃洋。
(6) 部分會(huì)把棧中存儲(chǔ)的兩個(gè)參數(shù) (456 和 123) 進(jìn)行銷(xiāo)毀處理传货。雖然通過(guò)兩次的 pop 指令也可以實(shí)現(xiàn),不過(guò)采用 esp 寄存器 + 8 的方式會(huì)更有效率(處理 1 次即可)宏娄。對(duì)棧進(jìn)行數(shù)值的輸入和輸出時(shí)问裕,數(shù)值的單位是4字節(jié)。因此孵坚,通過(guò)在負(fù)責(zé)棧地址管理的 esp 寄存器中加上4的2倍8粮宛,就可以達(dá)到和運(yùn)行兩次 pop 命令同樣的效果。雖然內(nèi)存中的數(shù)據(jù)實(shí)際上還殘留著卖宠,但只要把 esp 寄存器的值更新為數(shù)據(jù)存儲(chǔ)地址前面的數(shù)據(jù)位置巍杈,該數(shù)據(jù)也就相當(dāng)于銷(xiāo)毀了。
我在編譯 Sample4.c
文件時(shí)逗堵,出現(xiàn)了下圖的這條消息
圖中的意思是指 c 的值在 MyFunc 定義了但是一直未被使用秉氧,這其實(shí)是一項(xiàng)編譯器優(yōu)化的功能,由于存儲(chǔ)著 AddNum 函數(shù)返回值的變量 c 在后面沒(méi)有被用到蜒秤,因此編譯器就認(rèn)為 該變量沒(méi)有意義汁咏,進(jìn)而也就沒(méi)有生成與之對(duì)應(yīng)的匯編語(yǔ)言代碼。
下圖是調(diào)用 AddNum 這一函數(shù)前后棧內(nèi)存的變化
函數(shù)的內(nèi)部處理
上面我們用匯編代碼分析了一下 Sample4.c 整個(gè)過(guò)程的代碼作媚,現(xiàn)在我們著重分析一下 AddNum 函數(shù)的源代碼部分攘滩,分析一下參數(shù)的接收、返回值和返回等機(jī)制
_AddNum proc near
push ebp -----------(1)
mov ebp,esp -----------(2)
mov eax,dword ptr[ebp+8] -----------(3)
add eax,dword ptr[ebp+12] -----------(4)
pop ebp -----------(5)
ret ----------------------------------(6)
_AddNum endp
ebp 寄存器的值在(1)中入棧纸泡,在(5)中出棧漂问,這主要是為了把函數(shù)中用到的 ebp 寄存器的內(nèi)容,恢復(fù)到函數(shù)調(diào)用前的狀態(tài)。
(2) 中把負(fù)責(zé)管理?xiàng)5刂返?esp 寄存器的值賦值到了 ebp 寄存器中蚤假。這是因?yàn)槔敢?mov 指令中方括號(hào)內(nèi)的參數(shù),是不允許指定 esp 寄存器的磷仰。因此袍嬉,這里就采用了不直接通過(guò) esp,而是用 ebp 寄存器來(lái)讀寫(xiě)棧內(nèi)容的方法灶平。
(3) 使用[ebp + 8] 指定棧中存儲(chǔ)的第1個(gè)參數(shù)123伺通,并將其讀出到 eax 寄存器中。像這樣逢享,不使用 pop 指令涤浇,也可以參照棧的內(nèi)容蜕企。而之所以從多個(gè)寄存器中選擇了 eax 寄存器,是因?yàn)?eax 是負(fù)責(zé)運(yùn)算的累加寄存器。
通過(guò)(4) 的 add 指令伍茄,把當(dāng)前 eax 寄存器的值同第2個(gè)參數(shù)相加后的結(jié)果存儲(chǔ)在 eax 寄存器中慨菱。[ebp + 12] 是用來(lái)指定第2個(gè)參數(shù)456的残揉。在 C 語(yǔ)言中昏苏,函數(shù)的返回值必須通過(guò) eax 寄存器返回,這也是規(guī)定俊犯。也就是 函數(shù)的參數(shù)是通過(guò)棧來(lái)傳遞妇多,返回值是通過(guò)寄存器返回的。
(6) 中 ret 指令運(yùn)行后燕侠,函數(shù)返回目的地內(nèi)存地址會(huì)自動(dòng)出棧
者祖,據(jù)此,程序流程就會(huì)跳轉(zhuǎn)返回到(6) (Call _AddNum)
的下一行绢彤。這時(shí)七问,AddNum 函數(shù)入口和出口處棧的狀態(tài)變化,就如下圖所示
全局變量和局部變量
在熟悉了匯編語(yǔ)言后茫舶,接下來(lái)我們來(lái)了解一下全局變量和局部變量械巡,在函數(shù)外部定義的變量稱(chēng)為全局變量
,在函數(shù)內(nèi)部定義的變量稱(chēng)為局部變量
饶氏,全局變量可以在任意函數(shù)中使用讥耗,局部變量只能在函數(shù)定義局部變量的內(nèi)部使用。下面疹启,我們就通過(guò)匯編語(yǔ)言來(lái)看一下全局變量和局部變量的不同之處古程。
下面定義的 C 語(yǔ)言代碼分別定義了局部變量和全局變量,并且給各變量進(jìn)行了賦值喊崖,我們先看一下源代碼部分
// 定義被初始化的全局變量
int a1 = 1;
int a2 = 2;
int a3 = 3;
int a4 = 4;
int a5 = 5;
// 定義沒(méi)有初始化的全局變量
int b1,b2,b3,b4,b5;
// 定義函數(shù)
void MyFunc(){
// 定義局部變量
int c1,c2,c3,c4,c5,c6,c7,c8,c9,c10;
// 給局部變量賦值
c1 = 1;
c2 = 2;
c3 = 3;
c4 = 4;
c5 = 5;
c6 = 6;
c7 = 7;
c8 = 8;
c9 = 9;
c10 = 10;
// 把局部變量賦值給全局變量
a1 = c1;
a2 = c2;
a3 = c3;
a4 = c4;
a5 = c5;
b1 = c6;
b2 = c7;
b3 = c8;
b4 = c9;
b5 = c10;
}
上面的代碼挺暴力的挣磨,不過(guò)沒(méi)關(guān)系雇逞,能夠便于我們分析其匯編源碼就好,我們用 Borland C++ 編譯后的匯編代碼如下茁裙,編譯完成后的源碼比較長(zhǎng)塘砸,這里我們只拿出來(lái)一部分作為分析使用(我們改變了一下段定義順序,刪除了部分注釋?zhuān)?/p>
_DATA segment dword public use32 'DATA'
align 4
_a1 label dword
dd 1
align 4
_a2 label dword
dd 2
align 4
_a3 label dword
dd 3
align 4
_a4 label dword
dd 4
align 4
_a5 label dword
dd 5
_DATA ends
_BSS segment dword public use32 'BSS'
align 4
_b1 label dword
db 4 dup(?)
align 4
_b2 label dword
db 4 dup(?)
align 4
_b3 label dword
db 4 dup(?)
align 4
_b4 label dword
db 4 dup(?)
align 4
_b5 label dword
db 4 dup(?)
_BSS ends
_TEXT segment dword public use32 'CODE'
_MyFunc proc near
push ebp
mov ebp,esp
add esp,-20
push ebx
push esi
mov eax,1
mov edx,2
mov ecx,3
mov ebx,4
mov esi,5
mov dword ptr [ebp-4],6
mov dword ptr [ebp-8],7
mov dword ptr [ebp-12],8
mov dword ptr [ebp-16],9
mov dword ptr [ebp-20],10
mov dword ptr [_a1],eax
mov dword ptr [_a2],edx
mov dword ptr [_a3],ecx
mov dword ptr [_a4],ebx
mov dword ptr [_a5],esi
mov eax,dword ptr [ebp-4]
mov dword ptr [_b1],eax
mov edx,dword ptr [ebp-8]
mov dword ptr [_b2],edx
mov ecx,dword ptr [ebp-12]
mov dword ptr [_b3],ecx
mov eax,dword ptr [ebp-16]
mov dword ptr [_b4],eax
mov edx,dword ptr [ebp-20]
mov dword ptr [_b5],edx
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
_MyFunc endp
_TEXT ends
編譯后的程序呜达,會(huì)被歸類(lèi)到名為段定義的組谣蠢。
- 初始化的全局變量,會(huì)匯總到名為 _DATA 的段定義中
_DATA segment dword public use32 'DATA'
...
_DATA ends
- 沒(méi)有初始化的全局變量查近,會(huì)匯總到名為 _BSS 的段定義中
_BSS segment dword public use32 'BSS'
...
_BSS ends
- 被段定義 _TEXT 圍起來(lái)的匯編代碼則是 Borland C++ 的定義
_TEXT segment dword public use32 'CODE'
_MyFunc proc near
...
_MyFunc endp
_TEXT ends
我們?cè)诜治錾厦鎱R編代碼之前,先來(lái)認(rèn)識(shí)一下更多的匯編指令挤忙,此表是對(duì)上面部分操作碼及其功能的接續(xù)
操作碼 | 操作數(shù) | 功能 |
---|---|---|
add | A,B | 把A和B的值相加霜威,并把結(jié)果賦值給A |
call | A | 調(diào)用函數(shù)A |
cmp | A,B | 對(duì)A和B進(jìn)行比較,比較結(jié)果會(huì)自動(dòng)存入標(biāo)志寄存器中 |
inc | A | 對(duì)A的值 + 1 |
ige | 標(biāo)簽名 | 和 cmp 命令組合使用册烈。跳轉(zhuǎn)到標(biāo)簽行 |
jl | 標(biāo)簽名 | 和 cmp 命令組合使用戈泼。跳轉(zhuǎn)到標(biāo)簽行 |
jle | 標(biāo)簽名 | 和 cmp 命令組合使用。跳轉(zhuǎn)到標(biāo)簽行 |
jmp | 標(biāo)簽名 | 和 cmp 命令組合使用赏僧。跳轉(zhuǎn)到標(biāo)簽行 |
mov | A,B | 把 B 的值賦給 A |
pop | A | 從棧中讀取數(shù)值并存入A |
push | A | 把A的值存入棧中 |
ret | 無(wú) | 將處理返回到調(diào)用源 |
xor | A,B | A和B的位進(jìn)行亦或比較大猛,并將結(jié)果存入A中 |
我們首先來(lái)看一下 _DATA
段定義的內(nèi)容。_a1 label dword
定義了 _a1
這個(gè)標(biāo)簽淀零。標(biāo)簽表示的是相對(duì)于段定義起始位置的位置挽绩。由于_a1
在 _DATA 段
定義的開(kāi)頭位置,所以相對(duì)位置是0驾中。 _a1
就相當(dāng)于是全局變量a1唉堪。編譯后的函數(shù)名和變量名前面會(huì)加一個(gè)(_)
,這也是 Borland C++ 的規(guī)定肩民。dd 1
指的是唠亚,申請(qǐng)分配了4字節(jié)的內(nèi)存空間,存儲(chǔ)著1這個(gè)初始值持痰。 dd指的是 define double word
表示有兩個(gè)長(zhǎng)度為2的字節(jié)領(lǐng)域(word)灶搜,也就是4字節(jié)的意思。
Borland C++ 中工窍,由于int
類(lèi)型的長(zhǎng)度是4字節(jié)割卖,因此匯編器就把 int a1 = 1 變換成了 _a1 label dword 和 dd 1
。同樣移剪,這里也定義了相當(dāng)于全局變量的 a2 - a5 的標(biāo)簽 _a2 - _a5
究珊,它們各自的初始值 2 - 5 也被存儲(chǔ)在各自的4字節(jié)中。
接下來(lái)纵苛,我們來(lái)說(shuō)一說(shuō) _BSS
段定義的內(nèi)容剿涮。這里定義了相當(dāng)于全局變量 b1 - b5 的標(biāo)簽 _b1 - _b5
言津。其中的db 4dup(?)
表示的是申請(qǐng)分配了4字節(jié)的領(lǐng)域,但值尚未確定(這里用 ? 來(lái)表示)的意思取试。db(define byte)
表示有1個(gè)長(zhǎng)度是1字節(jié)的內(nèi)存空間悬槽。因而,db 4 dup(?) 的情況下瞬浓,就是4字節(jié)的內(nèi)存空間初婆。
注意:db 4 dup(?) 不要和 dd 4 混淆了,前者表示的是4個(gè)長(zhǎng)度是1字節(jié)的內(nèi)存空間猿棉。而 db 4 表示的則是雙字節(jié)( = 4 字節(jié)) 的內(nèi)存空間中存儲(chǔ)的值是 4
臨時(shí)確保局部變量使用的內(nèi)存空間
我們知道磅叛,局部變量是臨時(shí)保存在寄存器和棧中的。函數(shù)內(nèi)部利用棧進(jìn)行局部變量的存儲(chǔ)萨赁,函數(shù)調(diào)用完成后弊琴,局部變量值被銷(xiāo)毀,但是寄存器可能用于其他目的杖爽。所以敲董,局部變量只是函數(shù)在處理期間臨時(shí)存儲(chǔ)在寄存器和棧中的。
回想一下上述代碼是不是定義了10個(gè)局部變量慰安?這是為了表示存儲(chǔ)局部變量的不僅僅是棧腋寨,還有寄存器。為了確保 c1 - c10 所需的域化焕,寄存器空閑的時(shí)候就會(huì)使用寄存器萄窜,寄存器空間不足的時(shí)候就會(huì)使用棧。
讓我們繼續(xù)來(lái)分析上面代碼的內(nèi)容锣杂。_TEXT
段定義表示的是 MyFunc
函數(shù)的范圍脂倦。在 MyFunc 函數(shù)中定義的局部變量所需要的內(nèi)存領(lǐng)域。會(huì)被盡可能的分配在寄存器中元莫。大家可能認(rèn)為使用高性能的寄存器來(lái)替代普通的內(nèi)存是一種資源浪費(fèi)赖阻,但是編譯器不這么認(rèn)為,只要寄存器有空間踱蠢,編譯器就會(huì)使用它火欧。由于寄存器的訪(fǎng)問(wèn)速度遠(yuǎn)高于內(nèi)存,所以直接訪(fǎng)問(wèn)寄存器能夠高效的處理茎截。局部變量使用寄存器苇侵,是 Borland C++ 編譯器最優(yōu)化的運(yùn)行結(jié)果。
代碼清單中的如下內(nèi)容表示的是向寄存器中分配局部變量的部分
mov eax,1
mov edx,2
mov ecx,3
mov ebx,4
mov esi,5
僅僅對(duì)局部變量進(jìn)行定義是不夠的企锌,只有在給局部變量賦值時(shí)榆浓,才會(huì)被分配到寄存器的內(nèi)存區(qū)域。上述代碼相當(dāng)于就是給5個(gè)局部變量 c1 - c5 分別賦值為 1 - 5撕攒。eax陡鹃、edx烘浦、ecx、ebx萍鲸、esi 是 x86 系列32位 CPU 寄存器的名稱(chēng)闷叉。至于使用哪個(gè)寄存器,是由編譯器
來(lái)決定的 脊阴。
x86 系列 CPU 擁有的寄存器中握侧,程序可以操作的是十幾,其中空閑的最多會(huì)有幾個(gè)嘿期。因而品擎,局部變量超過(guò)寄存器數(shù)量的時(shí)候,可分配的寄存器就不夠用了秽五,這種情況下孽查,編譯器就會(huì)把棧派上用場(chǎng),用來(lái)存儲(chǔ)剩余的局部變量坦喘。
在上述代碼這一部分,給局部變量c1 - c5 分配完寄存器后西设,可用的寄存器數(shù)量就不足了瓣铣。于是,剩下的5個(gè)局部變量c6 - c10 就被分配給了棧的內(nèi)存空間贷揽。如下面代碼所示
mov dword ptr [ebp-4],6
mov dword ptr [ebp-8],7
mov dword ptr [ebp-12],8
mov dword ptr [ebp-16],9
mov dword ptr [ebp-20],10
函數(shù)入口 add esp,-20
指的是棠笑,對(duì)棧數(shù)據(jù)存儲(chǔ)位置的 esp 寄存器(棧指針)的值做減20的處理。為了確保內(nèi)存變量 c6 - c10 在棧中禽绪,就需要保留5個(gè) int 類(lèi)型的局部變量(4字節(jié) * 5 = 20 字節(jié))所需的空間蓖救。mov ebp,esp
這行指令表示的意思是將 esp 寄存器的值賦值到 ebp 寄存器。之所以需要這么處理印屁,是為了通過(guò)在函數(shù)出口處 mov esp ebp
這一處理循捺,把 esp 寄存器的值還原到原始狀態(tài),從而對(duì)申請(qǐng)分配的椥廴耍空間進(jìn)行釋放从橘,這時(shí)棧中用到的局部變量就消失了。這也是棧的清理處理础钠。在使用寄存器的情況下恰力,局部變量則會(huì)在寄存器被用于其他用途時(shí)自動(dòng)消失,如下圖所示旗吁。
mov dword ptr [ebp-4],6
mov dword ptr [ebp-8],7
mov dword ptr [ebp-12],8
mov dword ptr [ebp-16],9
mov dword ptr [ebp-20],10
這五行代碼是往棽任空間代入數(shù)值的部分,由于在向棧申請(qǐng)內(nèi)存空間前很钓,借助了 mov ebp, esp
這個(gè)處理香府,esp 寄存器的值被保存到了 esp 寄存器中董栽,因此,通過(guò)使用[ebp - 4]回还、[ebp - 8]裆泳、[ebp - 12]、[ebp - 16]柠硕、[ebp - 20] 這樣的形式工禾,就可以申請(qǐng)分配20字節(jié)的棧內(nèi)存空間切分成5個(gè)長(zhǎng)度為4字節(jié)的空間來(lái)使用。例如蝗柔,mov dword ptr [ebp-4],6
表示的就是闻葵,從申請(qǐng)分配的內(nèi)存空間的下端(ebp寄存器指示的位置)開(kāi)始向前4字節(jié)的地址([ebp - 4]) 中,存儲(chǔ)著6這一4字節(jié)數(shù)據(jù)癣丧。
循環(huán)控制語(yǔ)句的處理
上面說(shuō)的都是順序流程槽畔,那么現(xiàn)在就讓我們分析一下循環(huán)流程的處理,看一下 for 循環(huán)
以及 if 條件分支
等 c 語(yǔ)言程序的 流程控制
是如何實(shí)現(xiàn)的胁编,我們還是以代碼以及編譯后的結(jié)果為例厢钧,看一下程序控制流程的處理過(guò)程。
// 定義MySub 函數(shù)
void MySub(){
// 不做任何處理
}
// 定義MyFunc 函數(shù)
void Myfunc(){
int i;
for(int i = 0;i < 10;i++){
// 重復(fù)調(diào)用MySub十次
MySub();
}
}
上述代碼將局部變量 i 作為循環(huán)條件嬉橙,循環(huán)調(diào)用十次MySub
函數(shù)早直,下面是它主要的匯編代碼
xor ebx, ebx ; 將寄存器清0
@4 call _MySub ; 調(diào)用MySub函數(shù)
inc ebx ; ebx寄存器的值 + 1
cmp ebx,10 ; 將ebx寄存器的值和10進(jìn)行比較
jl short @4 ; 如果小于10就跳轉(zhuǎn)到 @4
C 語(yǔ)言中的 for 語(yǔ)句是通過(guò)在括號(hào)中指定循環(huán)計(jì)數(shù)器的初始值(i = 0)、循環(huán)的繼續(xù)條件(i < 10)市框、循環(huán)計(jì)數(shù)器的更新(i++) 這三種形式來(lái)進(jìn)行循環(huán)處理的霞扬。與此相對(duì)的匯編代碼就是通過(guò)比較指令(cmp)
和 跳轉(zhuǎn)指令(jl)
來(lái)實(shí)現(xiàn)的。
下面我們來(lái)對(duì)上述代碼進(jìn)行說(shuō)明
MyFunc
函數(shù)中用到的局部變量只有 i 枫振,變量 i 申請(qǐng)分配了 ebx 寄存器的內(nèi)存空間喻圃。for 語(yǔ)句括號(hào)中的 i = 0 被轉(zhuǎn)換為 xor ebx,ebx
這一處理,xor 指令會(huì)對(duì)左起第一個(gè)操作數(shù)和右起第二個(gè)操作數(shù)進(jìn)行 XOR 運(yùn)算粪滤,然后把結(jié)果存儲(chǔ)在第一個(gè)操作數(shù)中斧拍。由于這里把第一個(gè)操作數(shù)和第二個(gè)操作數(shù)都指定為了 ebx,因此就變成了對(duì)相同數(shù)值的 XOR 運(yùn)算额衙。也就是說(shuō)不管當(dāng)前寄存器的值是什么饮焦,最終的結(jié)果都是0。類(lèi)似的窍侧,我們使用 mov ebx,0
也能得到相同的結(jié)果县踢,但是 xor 指令的處理速度更快,而且編譯器也會(huì)啟動(dòng)最優(yōu)化功能伟件。
XOR 指的就是異或操作硼啤,它的運(yùn)算規(guī)則是 如果a、b兩個(gè)值不相同斧账,則異或結(jié)果為1谴返。如果a煞肾、b兩個(gè)值相同,異或結(jié)果為0嗓袱。
相同數(shù)值進(jìn)行 XOR 運(yùn)算籍救,運(yùn)算結(jié)果為0。XOR 的運(yùn)算規(guī)則是渠抹,值不同時(shí)結(jié)果為1蝙昙,值相同時(shí)結(jié)果為0。例如 01010101 和 01010101 進(jìn)行運(yùn)算梧却,就會(huì)分別對(duì)各個(gè)數(shù)字位進(jìn)行 XOR 運(yùn)算奇颠。因?yàn)槊總€(gè)數(shù)字位都相同,所以運(yùn)算結(jié)果為0放航。
ebx 寄存器的值初始化后烈拒,會(huì)通過(guò) call 指定調(diào)用 _MySub 函數(shù),從 _MySub 函數(shù)返回后广鳍,會(huì)執(zhí)行inc ebx
指令荆几,對(duì) ebx 的值進(jìn)行 + 1 操作,這個(gè)操作就相當(dāng)于 i++ 的意思赊时,++ 表示的就是當(dāng)前數(shù)值 + 1伴郁。
這里需要知道 i++ 和 ++i 的區(qū)別
i++ 是先賦值,復(fù)制完成后再對(duì) i執(zhí)行 + 1 操作
++i 是先進(jìn)行 +1 操作蛋叼,完成后再進(jìn)行賦值
inc
下一行的 cmp
是用來(lái)對(duì)第一個(gè)操作數(shù)和第二個(gè)操作數(shù)的數(shù)值進(jìn)行比較的指令。 cmp ebx,10
就相當(dāng)于 C 語(yǔ)言中的 i < 10 這一處理剂陡,意思是把 ebx 寄存器的值與10進(jìn)行比較狈涮。匯編語(yǔ)言中比較指令的結(jié)果,會(huì)存儲(chǔ)在 CPU 的標(biāo)志寄存器中鸭栖。不過(guò)歌馍,標(biāo)志寄存器的值,程序是無(wú)法直接參考的晕鹊。那如何判斷比較結(jié)果呢松却?
匯編語(yǔ)言中有多個(gè)跳轉(zhuǎn)指令
,這些跳轉(zhuǎn)指令會(huì)根據(jù)標(biāo)志寄存器的值來(lái)判斷是否進(jìn)行跳轉(zhuǎn)操作溅话,例如最后一行的 jl晓锻,它會(huì)根據(jù) cmp ebx,10 指令所存儲(chǔ)在標(biāo)志寄存器中的值來(lái)判斷是否跳轉(zhuǎn),jl
這條指令表示的就是 jump on less than(小于的話(huà)就跳轉(zhuǎn))
飞几。發(fā)現(xiàn)如果 i 比 10 小砚哆,就會(huì)跳轉(zhuǎn)到 @4 所在的指令處繼續(xù)執(zhí)行。
那么匯編代碼的意思也可以用 C 語(yǔ)言來(lái)改寫(xiě)一下屑墨,加深理解
i ^= i;
L4: MySub();
i++;
if(i < 10) goto L4;
代碼第一行 i ^= i 指的就是 i 和 i 進(jìn)行異或運(yùn)算躁锁,也就是 XOR 運(yùn)算纷铣,MySub() 函數(shù)用 L4 標(biāo)簽來(lái)替代,然后進(jìn)行 i 自增操作战转,如果i 的值小于 10 的話(huà)搜立,就會(huì)一直循環(huán) MySub() 函數(shù)。
條件分支的處理方法
條件分支的處理方式和循環(huán)的處理方式很相似槐秧,使用的也是 cmp 指令和跳轉(zhuǎn)指令啄踊。下面是用 C 語(yǔ)言編寫(xiě)的條件分支的代碼
// 定義MySub1 函數(shù)
void MySub1(){
// 不做任何處理
}
// 定義MySub2 函數(shù)
void MySub2(){
// 不做任何處理
}
// 定義MySub3 函數(shù)
void MySub3(){
// 不做任何處理
}
// 定義MyFunc 函數(shù)
void MyFunc(){
int a = 123;
// 根據(jù)條件調(diào)用不同的函數(shù)
if(a > 100){
MySub1();
}
else if(a < 50){
MySub2();
}
else
{
MySub3();
}
}
很簡(jiǎn)單的一個(gè)實(shí)現(xiàn)了條件判斷的 C 語(yǔ)言代碼,那么我們把它用 Borland C++ 編譯之后的結(jié)果如下
_MyFunc proc near
push ebp
mov ebp,esp
mov eax,123 ; 把123存入 eax 寄存器中
cmp eax,100 ; 把 eax 寄存器的值同100進(jìn)行比較
jle short @8 ; 比100小時(shí)色鸳,跳轉(zhuǎn)到@8標(biāo)簽
call _MySub1 ; 調(diào)用MySub1函數(shù)
jmp short @11 ; 跳轉(zhuǎn)到@11標(biāo)簽
@8:
cmp eax,50 ; 把 eax 寄存器的值同50進(jìn)行比較
jge short @10 ; 比50大時(shí)社痛,跳轉(zhuǎn)到@10標(biāo)簽
call _MySub2 ; 調(diào)用MySub2函數(shù)
jmp short @11 ; 跳轉(zhuǎn)到@11標(biāo)簽
@10:
call _MySub3 ; 調(diào)用MySub3函數(shù)
@11:
pop ebp
ret
_MyFunc endp
上面代碼用到了三種跳轉(zhuǎn)指令,分別是jle(jump on less or equal)
比較結(jié)果小時(shí)跳轉(zhuǎn)命雀,jge(jump on greater or equal)
比較結(jié)果大時(shí)跳轉(zhuǎn)蒜哀,還有不管結(jié)果怎樣都會(huì)進(jìn)行跳轉(zhuǎn)的jmp
,在這些跳轉(zhuǎn)指令之前還有用來(lái)比較的指令 cmp
吏砂,構(gòu)成了上述匯編代碼的主要邏輯形式撵儿。
了解程序運(yùn)行邏輯的必要性
通過(guò)對(duì)上述匯編代碼和 C 語(yǔ)言源代碼進(jìn)行比較,想必大家對(duì)程序的運(yùn)行方式有了新的理解狐血,而且淀歇,從匯編源代碼中獲取的知識(shí),也有助于了解 Java 等高級(jí)語(yǔ)言的特性匈织,比如 Java 中就有 native 關(guān)鍵字修飾的變量浪默,那么這個(gè)變量的底層就是使用 C 語(yǔ)言編寫(xiě)的,還有一些 Java 中的語(yǔ)法糖只有通過(guò)匯編代碼才能知道其運(yùn)行邏輯缀匕。在某些情況下纳决,對(duì)于查找 bug 的原因也是有幫助的。
上面我們了解到的編程方式都是串行處理的乡小,那么串行處理有什么特點(diǎn)呢阔加?
串行處理最大的一個(gè)特點(diǎn)就是專(zhuān)心只做一件事情
,一件事情做完之后才會(huì)去做另外一件事情满钟。
計(jì)算機(jī)是支持多線(xiàn)程的胜榔,多線(xiàn)程的核心就是 CPU切換,如下圖所示
我們還是舉個(gè)實(shí)際的例子湃番,讓我們來(lái)看一段代碼
// 定義全局變量
int counter = 100;
// 定義MyFunc1()
void MyFunc(){
counter *= 2;
}
// 定義MyFunc2()
void MyFunc2(){
counter *= 2;
}
上述代碼是更新 counter 的值的 C 語(yǔ)言程序夭织,MyFunc1() 和 MyFunc2() 的處理內(nèi)容都是把 counter 的值擴(kuò)大至原來(lái)的二倍,然后再把 counter 的值賦值給 counter 牵辣。這里摔癣,我們假設(shè)使用多線(xiàn)程處理
,同時(shí)調(diào)用了一次MyFunc1 和 MyFunc2 函數(shù),這時(shí)择浊,全局變量 counter 的值戴卜,理應(yīng)編程 100 * 2 * 2 = 400。如果你開(kāi)啟了多個(gè)線(xiàn)程的話(huà)琢岩,你會(huì)發(fā)現(xiàn) counter 的數(shù)值有時(shí)也是 200投剥,對(duì)于為什么出現(xiàn)這種情況,如果你不了解程序的運(yùn)行方式担孔,是很難找到原因的江锨。
我們將上面的代碼轉(zhuǎn)換成匯編語(yǔ)言的代碼如下
mov eax,dword ptr[_counter] ; 將 counter 的值讀入 eax 寄存器
add eax,eax ; 將 eax 寄存器的值擴(kuò)大2倍。
mov dword ptr[_counter],eax ; 將 eax 寄存器的值存入 counter 中糕篇。
在多線(xiàn)程程序中啄育,用匯編語(yǔ)言表示的代碼每運(yùn)行一行,處理都有可能切換到其他線(xiàn)程中拌消。因而挑豌,假設(shè) MyFun1 函數(shù)在讀出 counter 數(shù)值100后,還未來(lái)得及將它的二倍值200寫(xiě)入 counter 時(shí)墩崩,正巧 MyFun2 函數(shù)讀出了 counter 的值100氓英,那么結(jié)果就將變?yōu)?200 。
為了避免該bug鹦筹,我們可以采用以函數(shù)或 C 語(yǔ)言代碼的行為單位來(lái)禁止線(xiàn)程切換的鎖定
方法铝阐,或者使用某種線(xiàn)程安全的方式來(lái)避免該問(wèn)題的出現(xiàn)。
現(xiàn)在基本上沒(méi)有人用匯編語(yǔ)言來(lái)編寫(xiě)程序了铐拐,因?yàn)?C徘键、Java等高級(jí)語(yǔ)言的效率要比匯編語(yǔ)言快很多。不過(guò)遍蟋,匯編語(yǔ)言的經(jīng)驗(yàn)還是很重要的啊鸭,通過(guò)借助匯編語(yǔ)言,我們可以更好的了解計(jì)算機(jī)運(yùn)行機(jī)制匿值。
文章參考
www.computerhope.com/jargon/m/me…
baike.baidu.com/item/隊(duì)列/145…
baike.baidu.com/item/環(huán)形緩沖器/…
《程序是怎樣跑起來(lái)的》
baike.baidu.com/item/匯編語(yǔ)言/6…
磁盤(pán)
磁盤(pán)緩存
虛擬內(nèi)存
www.digitaltrends.com/computing/w…
baike.baidu.com/item/內(nèi)存/103…