亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C++ > 正文

用C語言來實現一個簡單的虛擬機

2020-05-23 14:18:42
字體:
來源:轉載
供稿:網友

這篇文章主要介紹了用C語言來實現一個簡單的虛擬機,其中棧數組的部分非常值得學習,需要的朋友可以參考下

必要的準備工作及注意事項:

在開始之前需要做以下工作:

一個C編譯器——我使用了 clang 3.4,也可以用其它支持 c99/c11 的編譯器;

文本編輯器——我建議使用基于IDE的文本編輯器,我使用 Emacs;

基礎編程知識——最基本的變量,流程控制,函數,數據結構等;

Make 腳本——能使程序更快一點。

為什么要寫個虛擬機?

有以下原因:

想深入了解計算機工作原理。本文將幫助你了解計算機底層如何工作,虛擬機提供簡潔的抽象層,這不就是一個最好的學習它們原理的方法嗎?

更深入了解一些編程語言是如何工作。例如,當下多種經常使用那些語言的虛擬機。包括JVM,Lua VM,FaceBook 的 Hip—Hop VM(PHP/Hack) 等。

只是因為有興趣學習虛擬機。

指令集

我們將要實現一種非常簡單的自定義的指令集。我不會講一些高級的如位移寄存器等,希望在讀過這篇文章后掌握這些。

我們的虛擬機具有一組寄存器,A,B,C,D,E, 和F。這些是通用寄存器,也就是說,它們可以用于存儲任何東西。一個程序將會是一個只讀指令序列。這個虛擬機是一個基于堆棧的虛擬機,也就是說它有一個可以讓我們壓入和彈出值的堆棧,同時還有少量可用的寄存器。這要比實現一個基于寄存器的虛擬機簡單的多。

言歸正傳,下面是我們將要實現的指令集:

 

 
  1. PSH 5 ; pushes 5 to the stack 
  2. PSH 10 ; pushes 10 to the stack 
  3. ADD ; pops two values on top of the stack, adds them pushes to stack 
  4. POP ; pops the value on the stack, will also print it for debugging 
  5. SET A 0 ; sets register A to 0 
  6. HLT ; stop the program 

這就是我們的指令集,注意,POP 指令將會打印我們彈出的指令,這樣我們就能夠看到 ADD 指令工作了。我還加入了一個 SET 指令,主要是讓你理解寄存器是可以訪問和寫入的。你也可以自己實現像MOV A B(將A的值移動到B)這樣的指令。HTL 指令是為了告訴我們程序已經運行結束。

虛擬機是如何工作的呢?

現在我們已經到了本文最關鍵的部分,虛擬機比你想象的簡單,它們遵循一個簡單的模式:讀取;解碼;執行。首先,我們從指令集合或代碼中讀取下一條指令,然后將指令解碼并執行解碼后的指令。為簡單起見,我們忽略了虛擬機的編碼部分,典型的虛擬機將會把一個指令(操作碼和它的操作數)打包成一個數字,然后再解碼這個指令。

項目結構

開始編程之前,我們需要設置好我們的項目。第一,你需要一個C編譯器(我使用 clang 3.4)。還需要一個文件夾來放置我們的項目,我喜歡將我的項目放置于~/Dev:

 

 
  1. $cd ~/Dev/ 
  2. mkdir mac 
  3. cd mac 
  4. mkdir src 

如上,我們先 cd 進入~/Dev 目錄,或者任何你想放置的位置,然后新建一個目錄(我稱這個虛擬機為"mac")。然后再 cd 進這個目錄并新建我們 src 目錄,這個目錄用于放置代碼。

Makefile

makefile 相對直接,我們不需要將什么東西分成多個文件,也不用包含任何東西,所以我們只需要用一些標志來編譯文件:

 

 
  1. SRC_FILES = main.c 
  2. CC_FLAGS = -Wall -Wextra -g -std=c11 
  3. CC = clang 
  4.  
  5. all: 
  6. ${CC} ${SRC_FILES} ${CC_FLAGS} -o mac 

這對目前來說已經足夠了,你以后還可以改進它,但是只要它能完成這個工作,我們應該滿足了。

指令編程(代碼)

現在開始寫虛擬機的代碼了。第一,我們需要定義程序的指令。為此,我們可以使用一個枚舉類型enum,因為我們的指令基本上是從0到X的數字。事實上,可以說你是在組裝一個匯編文件,它會使用像 mov 這樣的詞,然后翻譯成聲明的指令。

我們可以只寫一個指令文件,例如 PSH, 5 是0, 5,但是這樣并不易讀,所以我們使用枚舉器!

 

 
  1. typedef enum { 
  2. PSH, 
  3. ADD, 
  4. POP, 
  5. SET, 
  6. HLT 
  7. } InstructionSet; 

現在我們可以將一個測試程序存儲為一個數組。我們寫一個簡單的程序用于測試:將5和6相加,然后將他們打印出來(用POP指令)。如果你愿意,你可以定義一個指令將棧頂的值打印出來。

指令應該存儲成一個數組,我將在文檔的頂部定義它;但你或許會將它放在一個頭文件中,下面是我們的測試程序:

 

  1. const int program[] = { 
  2. PSH, 5, 
  3. PSH, 6, 
  4. ADD, 
  5. POP, 
  6. HLT 
  7. }; 

上面的程序將會把5和6壓入棧,調用 ADD 指令,這將會把棧頂的兩個值彈出,相加后將結果壓回棧中,接下來我們彈出結果,因為 POP 指令將會打印這個值,但是你不必自己再做了,我已經做好并測試過了。最后,HLT 指令結束程序。

很好,這樣我們有了自己的程序?,F在我們實現了虛擬機的讀取,解碼,求值的模式。但是要記住,我們沒有解碼任何東西,因為我們給出的是原始指令。也就是說我們只需要關注讀取和求值!我們可以將它們簡化成兩個函數 fetch 和 evaluate。

取得當前指令

因為我們已經將我們的程序存成了一個數組,所以很簡單的就可以取得當前指令。一個虛擬機有一個計數器,一般來說叫做程序計數器,指令指針等等,這些名字是一個意思取決于你的個人喜好。在虛擬機的代碼庫里,IP 或 PC 這樣的簡寫形式也隨處可見。

如果你之前有記得,我說過我們要把程序計數器以寄存器的形式存儲...我們將那么做——在以后?,F在,我們只是在我們代碼的最頂端創建一個叫 ip 的變量,并且設置為 0。

 

 
  1. int ip = 0; 

ip 變量代表指令指針。因為我們已經將程序存成了一個數組,所以使用 ip 變量去指明程序數組中當前索引。例如,如果創建了一個被賦值了程序 ip 索引的變量 x,它將存儲我們程序的第一條指令。

[假設ip為0]

 

 
  1. int ip = 0; 
  2.  
  3. int main() { 
  4. int instr = program[ip]; 
  5. return 0; 

如果我們打印變量 instr,本來應是 PSH 的它將顯示為0,因為在他是我們枚舉里的第一個值。我們也可以寫一個取回函數像這樣:

 

 
  1. int fetch() { 
  2. return program[ip]; 

這個函數將會返回當前被調用指令。太棒了,那么如果我們想要下一條指令呢?很容易,我們只要增加指令指針就好了:

 

 
  1. int main() { 
  2. int x = fetch(); // PSH 
  3. ip++; // increment instruction pointer 
  4. int y = fetch(); // 5 

那么怎樣讓它自己動起來呢?我們知道一個程序直到它執行 HLT 指令才會停止。因此我們使用一個無限的循環持續直到當前指令為HLT。

 

 
  1. // INCLUDE <stdbool.h>! 
  2. bool running = true
  3.  
  4. int main() { 
  5. while (running) { 
  6. int x = fetch(); 
  7. if (x == HLT) running = false
  8. ip++; 

這工作的很好,但是有點凌亂。我們正在循環每一條指令,檢查是否 HLT,如果是就停止循環,否則“吃掉”指令接著循環。

判斷一條指令

因此這就是我們虛擬機的主體,然而我們想要確實的評判每一條指令,并且使它更簡潔一些。好的,這個簡單的虛擬機,你可以寫一個“巨大”的 switch 聲明。讓 switch 中的每一個 case 對應一條我們定義在枚舉中的指令。這個 eval 函數將使用一個簡單的指令的參數來判斷。我們在函數中不會使用任何指令指針遞增除非我們想操作數浪費操作數。

 

 
  1. void eval(int instr) { 
  2. switch (instr) { 
  3. case HLT: 
  4. running = false
  5. break

因此如果我們在回到主函數,就可以像這樣使用我們的 eval 函數工作:

 

 
  1. bool running = true
  2. int ip = 0; 
  3.  
  4. // instruction enum here 
  5.  
  6. // eval function here 
  7.  
  8. // fetch function here 
  9.  
  10. int main() { 
  11. while (running) { 
  12. eval(fetch()); 
  13. ip++; // increment the ip every iteration 

棧!

很好,那會很完美的完成這個工作?,F在,在我們加入其他指令之前,我們需要一個棧。幸運的是,棧是很容易實現的,我們僅僅需要使用一個數組而已。數組會被設置為合適的大小,這樣它就能包含256個值了。我們也需要一個棧指針(常被縮寫為sp)。這個指針會指向棧數組。

為了讓我們對它有一個更加形象化的印象,讓我們來看看這個用數組實現的棧吧:

 

 
  1. [] // empty 
  2.  
  3. PSH 5 // put 5 on **top** of the stack 
  4. [5] 
  5.  
  6. PSH 6 
  7. [5, 6] 
  8.  
  9. POP 
  10. [5] 
  11.  
  12. POP 
  13. [] // empty 
  14.  
  15. PSH 6 
  16. [6] 
  17.  
  18. PSH 5 
  19. [6, 5] 

那么,在我們的程序里發生了什么呢?

 

 
  1. PSH, 5, 
  2. PSH, 6, 
  3. ADD, 
  4. POP, 
  5. HLT 

我們首先把5壓入了棧

 

 
  1. [5] 

然后壓入6:

 

 
  1. [5, 6] 

接著添加指令,取出這些值,把它們加在一起并把結果壓入棧中:

 

 
  1. [5, 6] 
  2.  
  3. // pop the top value, store it in a variable called a 
  4. a = pop; // a contains 6 
  5. [5] // stack contents 
  6.  
  7. // pop the top value, store it in a variable called b 
  8. b = pop; // b contains 5 
  9. [] // stack contents 
  10.  
  11. // now we add b and a. Note we do it backwards, in addition 
  12. // this doesn't matter, but in other potential instructions 
  13. // for instance divide 5 / 6 is not the same as 6 / 5 
  14. result = b + a; 
  15. push result // push the result to the stack 
  16. [11] // stack contents 

那么我們的棧指針在哪起作用呢?棧指針(或者說sp)一般是被設置為-1,這意味著這個指針是空的。請記住一個數組是從0開始的,如果沒有初始化sp的值,那么他會被設置為C編譯器放在那的一個隨機值。

如果我們將3個值壓棧,那么sp將變成2。所以這個數組保存了三個值:

sp指向這里(sp = 2)

|

V

[1, 5, 9]

0 1 2 <- 數組下標

現在我們從棧上出棧一次,我們僅需要減小棧頂指針。比如我們接下來把9出棧,那么棧頂將變為5:

sp指向這里(sp = 1)

|

V

[1, 5]

0 1 <- 數組下標

所以,當我們想知道棧頂內容的時候,只需要查看sp的當前值。OK,你可能想知道棧是如何工作的,現在我們用C語言實現它。很簡單,和ip一樣,我們也應該定義一個sp變量,記得把它賦為-1!再定義一個名為stack的數組,代碼如下:

 

 
  1. int ip = 0; 
  2. int sp = -1; 
  3. int stack[256]; // 用數組或適合此處的其它結構 
  4.  
  5. // 其它C代碼 

現在如果我們想入棧一個值,我們先增加棧頂指針,接著設置當前sp處的值(我們剛剛增加的)。注意:這兩步的順序很重要!

 

 
  1. // 壓棧5 
  2.  
  3. // sp = -1 
  4. sp++; // sp = 0 
  5. stack[sp] = 5; // 棧頂現在變為5 

所以,在我們的執行函數eval()里,可以像這樣實現push出棧指令:

 

 
  1. void eval(int instr) { 
  2. switch (instr) { 
  3. case HLT: { 
  4. running = false
  5. break
  6. case PSH: { 
  7. sp++; 
  8. stack[sp] = program[++ip]; 
  9. break

現在你看到,它和我們之前實現的eval()函數有一些不同。首先,我們把每個case語句塊放到大括號里。你可能不太了解這種用法,它可以讓你在每條case的作用域里定義變量。雖然現在不需要定義變量,但將來會用到。并且它可以很容易得讓所有的case語句塊保持一致的風格。

其次是神奇的表達式program[++ip]。它做了什么?呃,我們的程序存儲在一個數組里,PSH指令需要獲得一個操作數。操作數本質是一個參數,就像當你調用一個函數時,你可以給它傳遞一個參數。這種情況我們稱作壓棧數值5。我們可以通過增加指令指針(譯者注:一般也叫做程序計數器)ip來獲取操作數。當ip為0時,這意味著執行到了PSH指令,接下來我們希望取得下一條指令——即壓棧的數值。這可以通過ip自增的方法實現(注意:增加ip的位置十分重要,我們希望在取得指令前自增,否則我們只是拿到了PSH指令),接下來需要跳到下一條指令否則會引發奇怪的錯誤。當然我們也可以把sp++簡化到stack[++sp]里。

對于POP指令,實現非常簡單。只需要減小棧頂指針,但是我一般希望能夠在出棧的時候打印出棧值。

我省略了實現其它指令的代碼和swtich語句,僅列出POP指令的實現:

 

 
  1. // 記得#include <stdio.h>! 
  2.  
  3. case POP: { 
  4. int val_popped = stack[sp--]; 
  5. printf("popped %d/n", val_popped); 
  6. break

現在,POP指令能夠工作了!我們剛剛做的只是把棧頂放到變量val_popped里,接著棧頂指針減一。如果我們首先棧頂減一,那么將得到一些無效值,因為sp可能取值為0,那么我們可能把stack[-1]賦給val_popped,通常這不是一個好主意。

最后是ADD指令。這條指令可能要花費你一些腦細胞,同時這也是我們需要用大括號{}實現case語句內作用域的原因。

 

 
  1. case ADD: { 
  2. // 首先我們出棧,把數值存入變量a 
  3. int a = stack[sp--]; 
  4.  
  5. // 接著我們出棧,把數值存入變量b 
  6.  
  7. // 接著兩個變量相加,再把結果入棧 
  8. int result = a + b; 
  9. sp++; // 棧頂加1 **放在賦值之前** 
  10. stack[sp] = result; // 設置棧頂值 
  11.  
  12. // 完成! 
  13. break

寄存器

寄存器是虛擬機中的選配件,很容易實現。之前提到過我們可能需要六個寄存器:A,B,C,D,E和F。和實現指令集一樣,我們也用一個枚舉來實現它們。

 

 
  1. typedef enum { 
  2. A, B, C, D, E, F, 
  3. NUM_OF_REGISTERS 
  4. } Registers; 

小技巧:枚舉的最后放置了一個數 NUM_OF_REGISTERS。通過這個數可以獲取寄存器的個數,即便你又添加了其它的寄存器。現在我們需要一個數組為寄存器存放數值:

 

 
  1. int registers[NUM_OF_REGISTERS]; 

接下來你可以讀取寄存器內的值:

 

 
  1. printf("%d/n", registers[A]); // 打印寄存器A的值 

修訂

我沒有在寄存器花太多心思,但你應該能夠寫出一些操作寄存器的指令。比如,如果你想實現任何分支跳轉,可以通過把指令指針(譯者注:或叫程序計數器)和/或棧頂指針存到寄存器里,或者通過實現分支指令。

前者實現起來相對快捷、簡單。我們可以這樣做,增加代表IP和SP的寄存器:

 

 
  1. typedef enum { 
  2. A, B, C, D, E, F, PC, SP, 
  3. NUM_OF_REGISTERS 
  4. } Registers; 

現在我們需要實現代碼來使用指令指針和棧頂指針。一個簡單的辦法——刪掉上面定義的sp和ip變量,用宏定義實現它們:

 

 
  1. #define sp (registers[SP]) 
  2. #define ip (registers[IP]) 

譯者注:此處應同Registers枚舉中保持一致,IP應改為PC

這個修改恰到好處,你不需要重寫很多代碼,同時它工作的很好。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美在线不卡区| 欧美成年人视频网站| 日本一区二区不卡| 波霸ol色综合久久| 欧美极品欧美精品欧美视频| 国产精品第三页| 国产精品99久久久久久久久| 欧美国产日本高清在线| 中文字幕亚洲欧美日韩在线不卡| 欧美xxxx综合视频| 精品动漫一区二区三区| 亚洲视频在线观看免费| 久久国内精品一国内精品| 国外色69视频在线观看| 亚洲性线免费观看视频成熟| 国产99久久精品一区二区 夜夜躁日日躁| 欧美一区二区.| 欧美日韩性视频在线| 蜜月aⅴ免费一区二区三区| 久久精品影视伊人网| 欧美日韩亚洲视频一区| 久久精品国产欧美亚洲人人爽| 久久精品国产久精国产思思| 国产精品视频永久免费播放| 亚洲人成网站免费播放| 欧美国产精品人人做人人爱| 成人免费淫片视频软件| 欧美极品少妇xxxxⅹ免费视频| 色爱av美腿丝袜综合粉嫩av| 成人av电影天堂| 国产精品尤物福利片在线观看| 美女国内精品自产拍在线播放| 国产亚洲精品综合一区91| 亚洲一区美女视频在线观看免费| 国产精品夫妻激情| 国产精品久久久久久久av大片| 欧美午夜精品在线| 亚洲免费福利视频| 国a精品视频大全| 亚洲欧美制服中文字幕| 国产主播欧美精品| 日韩国产激情在线| 久久久成人精品视频| 视频直播国产精品| 中文字幕在线精品| 欧美日韩激情网| 亚洲美女又黄又爽在线观看| 国产精品高潮呻吟久久av野狼| 国产精品av在线| 在线视频国产日韩| 高清欧美性猛交xxxx黑人猛交| 亚洲自拍偷拍第一页| 久久久久久香蕉网| 亚洲天堂视频在线观看| 国产精品免费电影| 欧美高清视频在线观看| 欧美日韩人人澡狠狠躁视频| 精品日本高清在线播放| 8x海外华人永久免费日韩内陆视频| 国产精品久久久91| 永久免费看mv网站入口亚洲| 精品亚洲一区二区| 成人免费观看49www在线观看| 久久久久久国产精品久久| 欧美在线观看网址综合| 色偷偷888欧美精品久久久| www.日韩av.com| 福利一区视频在线观看| 欧美大肥婆大肥bbbbb| 中文字幕av日韩| 欧美成人激情图片网| 国产视频综合在线| 午夜精品在线视频| 欧美理论片在线观看| 国产精品久久久久不卡| 中文字幕在线成人| 久久成人精品一区二区三区| 久久精品一偷一偷国产| 亚洲视屏在线播放| 萌白酱国产一区二区| 伊人久久久久久久久久久久久| 国外成人在线播放| …久久精品99久久香蕉国产| 国产精品久久久久久av| 欧美日韩精品在线播放| 亚洲视频网站在线观看| 中文字幕亚洲欧美| 超碰日本道色综合久久综合| 亚洲综合日韩在线| 色中色综合影院手机版在线观看| 久久久亚洲网站| 尤物九九久久国产精品的特点| 姬川优奈aav一区二区| 国产精品一区二区av影院萌芽| 一色桃子一区二区| 亚洲国产精品资源| 欧美精品激情在线| 在线观看亚洲视频| 久久天天躁狠狠躁夜夜躁2014| 亚洲精品网站在线播放gif| 久久免费精品视频| 欧美巨乳在线观看| 日韩欧美国产网站| 色偷偷9999www| 亚洲精品www久久久| 国产视频欧美视频| 日韩美女视频免费在线观看| 中文字幕亚洲自拍| 久久人人爽人人| 欧美亚洲激情视频| 色综合伊人色综合网站| 欧美性生交xxxxxdddd| 国产精品久久久久久久久久小说| 成人激情在线播放| 日韩动漫免费观看电视剧高清| 伊是香蕉大人久久| 久久久久久久久电影| 欧美性高跟鞋xxxxhd| 国产精品三级久久久久久电影| 久久久久久久一区二区| 日本道色综合久久影院| 色在人av网站天堂精品| 亚洲精品综合久久中文字幕| 91系列在线观看| 日本精品va在线观看| 欧美日韩在线第一页| 亚洲性av在线| 日韩中文字幕免费视频| 亚洲成人久久一区| 日韩一区二区三区在线播放| 国产精品爱久久久久久久| 日韩av电影手机在线观看| 成人免费看吃奶视频网站| 欧美激情按摩在线| 欧美日韩另类在线| 亚洲加勒比久久88色综合| 92国产精品视频| 日韩中文字幕视频在线观看| 国产精品高潮呻吟久久av野狼| 亚洲欧美日韩天堂一区二区| 亚洲精品永久免费精品| 亚洲一级一级97网| 亚洲国产精品va在线看黑人| 性色av一区二区三区在线观看| 亚洲成人久久网| 国产精品久久久久久久久粉嫩av| 亚洲免费人成在线视频观看| 欧美日韩性生活视频| 欧美激情视频在线免费观看 欧美视频免费一| 亚洲伊人一本大道中文字幕| 91久久综合亚洲鲁鲁五月天| 日韩经典中文字幕在线观看| 色综合老司机第九色激情| 国产精品一久久香蕉国产线看观看| 日韩精品免费在线视频| 久久精品人人爽| 九九热r在线视频精品| 日韩欧美在线一区| 久久综合久久八八| 亚洲女人被黑人巨大进入| 韩国三级日本三级少妇99| 亚洲一区国产精品| 亚洲第一国产精品| 成人黄色生活片|