堆疊機的概念

堆疊機是指有堆疊資料儲存空間的電腦。所以堆疊機會有「推入堆疊」(stack push)和「從堆疊裡彈出」(stack pop)這兩個基本操作。推入就是在堆疊的最上面放上新的元素,彈出則是從堆疊的最上方取出元素。(譯註:堆疊是具有 LIFO 特性的一種資料結構。LIFO:Last-In-First-Out,最後放進的東西會最先被取出。)

堆疊機的運算,只對堆疊頂端的元素作用。舉例來說,堆疊機的ADD指令,會從堆疊頂端彈出兩個元素,將其加起來後把結果推入回堆疊(為了避免和 x86-64 指令混淆,假想堆疊機指令以大寫文字方式表示)。換句話說,ADD指令就是把堆疊頂端的2個元素,換成1個他們相加結果的元素的指令。

SUBMULDIV指令也和ADD一樣,是把堆疊頂端的2個元素,作減法、乘法和除法計算,用1個元素來取代的指令。

PUSH指令是把引數上的元素放到堆疊上的指令。還有現在不會用到的,從堆疊頂端取出1個元素的POP指令。

接下來,利用這些指令,我們來考慮一下2*3+4*5的計算。使用上述定義的堆疊機,我們應該可以如下列程式來計算2*3+4*5

// 計算2*3
PUSH 2
PUSH 3
MUL

// 計算4*5
PUSH 4
PUSH 5
MUL

// 計算2*3 + 4*5
ADD

來仔細看一下這段程式。假設堆疊裡已經放了某些值,這些值並不重要我們以「」來表示。堆疊成長的方向是由上往下。

開始的PUSH把2和3給推入堆疊裡,所以在執行MUL時堆疊裡的狀態是:

MUL從堆疊頂部取出2個元素,也就是32,相乘之後把其結果,也就是6給推進堆疊。底下是MUL執行後堆疊的狀態:

接著PUSH把4和5推進堆疊,所以在第2個MUL執行前堆疊變成下面這樣:

在這邊執行MUL45會被取出,被換成其相乘結果的20。底下是MUL執行後的狀態

可以發現現在堆疊裏面已經放好了2*34*5的結果。在這個狀態下執行ADD就會計算20+6,然後該結果被推進堆疊,最終堆疊會長這樣:

堆疊機的計算結果就是堆疊裡最後留下的值,而26就是2*3+4*5的答案,所以算式的計算結果是正確無誤的。

堆疊機不僅限於此式,而可以計算任意有暫時要紀錄答案的算式。使用堆疊機,任意算式的一部份,只要遵守將其執行結果放在堆疊的規定,按照上述的方法都能正確編譯出來。

小知識:CISC 和 RISC

x86-64是由1978年發售的8086逐步發展而成的指令集,也就是俗稱「CISC」派的處理器。CISC 處理器的特徵,是機械語言的計算不只可以對暫存器進行,也可以用在記憶體位址;還有機械語言的的指令長度可變;還有許多對組合語言的程式設計師來說很方便的,有許多把複雜的操作用1條指令來完成的指令。

而相對於 CISC 的,就是在1980年代發明的「RISC」。RISC 處理器的特徵,是運算只能對暫存器進行;對記憶體的操作只有讀取到暫存器、和從暫存器寫入記憶體2種;所有機械語言指令長度都一樣;不具有給組合語言的程式設計師方便的複合命令,只有讓編譯器使用的簡單指令等等。

x86-64是 CISC 少數活到今天的一支,除了x86-64以外幾乎都是以 RISC 為基礎設計的。具體來說像是 ARM、PowerPC、SPARC、MIPS、RISC-V 都是 RISC 處理器。

RISC 並沒有像是 CISC 那像可以在記憶體和暫存器之間進行運算的指令,暫存器也沒有別名(alias),更沒有特定整數暫存器有特別使用方法的規定。從這類指令集成為主流的今天看來,x86-64指令集看起來可能特別老舊。

RISC 處理器因為其單純的設計而容易加速,因而席捲業界。那位什麼x86-64能活下來呢?那是因為有著想要利用現有的軟體資產、追求高速x86處理器的大量市場需求,和 Intel 或 Intel 相容晶片廠商回應了這些需求所做的的技術革新。Intel 把CPU指令解碼,在x86指令在處理器內部轉換成某種 RISC 指令,把x86的內部架構改成 RISC 處理器,讓把 RISC 高速化的技術變成可以應用在x86之上。

Last updated