1. 程式人生 > >手把手教你做一個 C 語言編譯器(6):函式定義

手把手教你做一個 C 語言編譯器(6):函式定義

由於語法分析本身比較複雜,所以我們將它拆分成 3 個部分進行講解,分別是:變數定義、函式定義、表示式。本章講解函式定義相關的內容。

本系列:

EBNF 表示

這是上一章的 EBNF 方法中與函式定義相關的內容。

C
12345678910111213141516 variable_decl::=type{'*'}id{','{'*'}id}';'function_decl::=type{'*'}id'('parameter_decl')''{'body_decl'}'parameter_decl::=type{'*'}id{','type{'*'}id}body_decl::={variable_decl},{statement}statement::=non_empty_statement|empty_statement
non_empty_statement::=if_statement|while_statement|'{'statement'}'|'return'expression|expression';'if_statement::='if''('expression')'statement['else'non_empty_statement]while_statement::='while''('expression')'non_empty_statement

解析函式的定義

上一章的程式碼中,我們已經知道了什麼時候開始解析函式的定義,相關的程式碼如下:

C
1234567 ...if(token=='('){current_id[Class]=Fun;current_id[Value]=(int)(text+1);// the memory address of functionfunction_declaration();}else{...

即在這斷程式碼之前,我們已經為當前的識別符號(identifier)設定了正確的型別,上面這斷程式碼為當前的識別符號設定了正確的類別(Fun),以及該函式在程式碼段(text segment)中的位置。接下來開始解析函式定義相關的內容:parameter_decl 及 body_decl

函式引數與彙編程式碼

現在我們要回憶如何將“函式”轉換成對應的彙編程式碼,因為這決定了在解析時我們需要哪些相關的資訊。考慮下列函式:

C
123456 intdemo(intparam_a,int*param_b){intlocal_1;charlocal_2;...}

那麼它應該被轉換成什麼樣的彙編程式碼呢?在思考這個問題之前,我們需要了解當 demo函式被呼叫時,計算機的棧的狀態,如下(參照第三章講解的虛擬機器):

123456789101112131415 |....|high address+---------------+|arg:param_a|new_bp+3+---------------+|arg:param_b|new_bp+2+---------------+|returnaddress|new_bp+1+---------------+|old BP|<-newBP+---------------+|local_1|new_bp-1+---------------+|local_2|new_bp-2+---------------+|....|low address

這裡最為重要的一點是,無論是函式的引數(如 param_a)還是函式的區域性變數(如local_1)都是存放在計算機的  上的。因此,與存放在 資料段 中的全域性變數不同,在函式內訪問它們是通過 new_bp 指標和對應的位移量進行的。因此,在解析的過程中,我們需要知道引數的個數,各個引數的位移量。

函式定義的解析

這相當於是整個函式定義的語法解析的框架,程式碼如下:

C
1234567891011121314151617181920212223 voidfunction_declaration(){// type func_name (...) {...}//               | this partmatch('(');function_parameter();match(')');match('{');function_body();//match('}');                 //  ①// ②// unwind local variable declarations for all local variables.current_id=symbols;while(current_id[Token]){if(current_id[Class]==Loc){current_id[Class]=current_id[BClass];current_id[Type]=current_id[BType];current_id[Value]=current_id[BValue];}current_id=current_id+IdSize;}}

其中①中我們沒有消耗最後的}字元。這麼做的原因是:variable_decl 與 function_decl是放在一起解析的,而 variable_decl 是以字元 ; 結束的。而 function_decl 是以字元 }結束的,若在此通過 match 消耗了 ‘;’ 字元,那麼外層的 while 迴圈就沒法準確地知道函式定義已經結束。所以我們將結束符的解析放在了外層的 while 迴圈中。

而②中的程式碼是用於將符號表中的資訊恢復成全域性的資訊。這是因為,區域性變數是可以和全域性變數同名的,一旦同名,在函式體內區域性變數就會覆蓋全域性變數,出了函式體,全域性變數就恢復了原先的作用。這段程式碼線性地遍歷所有識別符號,並將儲存在 BXXX 中的資訊還原。

解析引數

C
1 parameter_decl::=type{'*'}id{','type{'*'}id}

解析函式的引數就是解析以逗號分隔的一個個識別符號,同時記錄它們的位置與型別。

C
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 intindex_of_bp;// index of bp pointer on stackvoidfunction_parameter(){inttype;intparams;params=0;while(token!=')'){// ①// int name, ...type=INT;if(token==Int){match(Int);}elseif(token==Char){type=CHAR;match(Char);}// pointer typewhile(token==Mul){match(Mul);type=type+PTR;}// parameter nameif(token!=Id){printf("%d: bad parameter declaration\n",line);exit(-1);}if(current_id[Class]==Loc){printf("%d: duplicate parameter declaration\n",line);exit(-1);}match(Id);//②// store the local variablecurrent_id[BClass]=current_id[Class];current_id[Class]=Loc;current_id[BType]=current_id[Type];current_id[Type]=type;current_id[BValue]=current_id[Value];current_id[Value]=params++;// index of current parameterif(token==','){match(',');}}// ③index_of_bp=params+1;}

其中①與全域性變數定義的解析十分一樣,用於解析該引數的型別。

而②則與上節中提到的“區域性變數覆蓋全域性變數”相關,先將全域性變數的資訊儲存(無論是是否真的在全域性中用到了這個變數)在 BXXX 中,再賦上區域性變數相關的資訊,如 Value 中存放的是引數的位置(是第幾個引數)。

③則與彙編程式碼的生成有關,index_of_bp 就是前文提到的 new_bp 的位置。

函式體的解析

我們實現的 C 語言與現代的 C 語言不太一致,我們需要所有的變數定義出現在所有的語句之前。函式體的程式碼如下:

C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263 voidfunction_body(){// type func_name (...) {...}//                   -->|   |<--// ... {// 1. local declarations// 2. statements// }

相關推薦

手把手一個 C 語言編譯器6函式定義

由於語法分析本身比較複雜,所以我們將它拆分成 3 個部分進行講解,分別是:變數定義、函式定義、表示式。本章講解函式定義相關的內容。 本系列: EBNF 表示 這是上一章的 EBNF 方法中與函式定義相關的內容。 C

手把手一個 C 語言編譯器5變數定義

本章中我們用 EBNF 來大致描述我們實現的 C 語言的文法,並實現其中解析變數定義部分。 由於語法分析本身比較複雜,所以我們將它拆分成 3 個部分進行講解,分別是:變數定義、函式定義、表示式。 本系列: EBNF 表示 EBNF 是對前一章提到的 BNF 的擴充

手把手一個 C 語言編譯器8表示式

這是整個編譯器的最後一部分,解析表示式。什麼是表示式?表示式是將各種語言要素的一個組合,用來求值。例如:函式呼叫、變數賦值、運算子運算等等。 表示式的解析難點有二:一是運算子的優先順序問題,二是如何將表示式編譯成目的碼。我們就來逐一說明。 本系列: 運算子的優先順

手把手一個 C 語言編譯器7語句

整個編譯器還剩下最後兩個部分:語句和表示式的解析。它們的內容比較多,主要涉及如何將語句和表示式編譯成彙編程式碼。這章講解語句的解析,相對於表示式來說它還是較為容易的。 本系列: 語句 C 語言區分“語句”(statement)和“表示式”(expression)兩

手把手一個 C 語言編譯器9總結

恭喜你完成了自己的 C 語言編譯器,本章中我們發一發牢騷,說一說編寫編譯器值得注意的一些問題;編寫編譯器時遇到的一些難題。 本系列: 虛擬機器與目的碼 整個系列的一開始,我們就著手虛擬機器的實現。不知道你是否有同感,這部分對於整個編譯器的編寫其實是十分重要的。我認

手把手一個 C 語言編譯器2虛擬機器

本章是“手把手教你構建 C 語言編譯器”系列的第三篇,本章我們要構建一臺虛擬的電腦,設計我們自己的指令集,執行我們的指令集,說得通俗一點就是自己實現一套匯編語言。它們將作為我們的編譯器最終輸出的目的碼。 本系列: 計算機的內部工作原理 我們關心計算機的三個基本部件

手把手一個 C 語言編譯器4遞迴下降

本章我們將講解遞迴下降的方法,並用它完成一個基本的四則運算的語法分析器。 本系列: 什麼是遞迴下降 傳統上,編寫語法分析器有兩種方法,一種是自頂向下,一種是自底自上。自頂向下是從起始非終結符開始,不斷地對非終結符進行分解,直到匹配輸入的終結符;自底向上是不斷地將終

手把手一個 C 語言編譯器3詞法分析器

本章我們要講解如何構建詞法分析器。 本系列: 什麼是詞法分析器 簡而言之,詞法分析器用於對原始碼字串做預處理,以減少語法分析器的複雜程度。 詞法分析器以原始碼字串為輸入,輸出為標記流(token stream),即一連串的標記,每個標記通常包括: (token,

手把手一個 C 語言編譯器0前言

“手把手教你構建 C 語言編譯器” 這一系列教程將帶你從頭編寫一個 C 語言的編譯器。希望通過這個系列,我們能對編譯器的構建有一定的瞭解,同時,我們也將構建出一個能用的 C 語言編譯器,儘管有許多語法並不支援。 在開始進入正題之前,本篇是一些閒聊,談談這個系列的初衷

手把手一個 C 語言編譯器1設計

本章是“手把手教你構建 C 語言編譯器”系列的第二篇,我們要從整體上講解如何設計我們的 C 語言編譯器。 本系列: 首先要說明的是,雖然標題是編譯器,但實際上我們構建的是 C 語言的直譯器,這意味著我們可以像執行指令碼一樣去執行 C 語言的原始碼檔案。這麼做的理由

手把手構建 C 語言編譯器6

由於語法分析本身比較複雜,所以我們將它拆分成 3 個部分進行講解,分別是:變數定義、函式定義、表示式。本章講解函式定義相關的內容。 手把手教你構建 C 語言編譯器系列共有10個部分: EBNF 表示 這是上一章的 EBNF 方法中與函式定義相關的內容。 variable_decl ::= type {'*

手把手藍芽聊天應用-藍芽連線模組

第4節 藍芽連線模組 藍芽連線的管理模組需要為ChatActivity提供於連線相關的所有功能,要設計的方便使用,並儘量隱藏連線的細節。 4.1 對外介面 我們首先來看看ConnectionManager需要向Chat Activity提供哪些介面。

手把手構建 C 語言編譯器4

本章我們將講解遞迴下降的方法,並用它完成一個基本的四則運算的語法分析器。 手把手教你構建 C 語言編譯器系列共有10個部分: 什麼是遞迴下降 傳統上,編寫語法分析器有兩種方法,一種是自頂向下,一種是自底向上。自頂向下是從起始非終結符開始,不斷地對非終結符進行分解,直到匹配輸入的終結符;自底向上是不斷地將終

手把手構建 C 語言編譯器8

這是整個編譯器的最後一部分,解析表示式。什麼是表示式?表示式是將各種語言要素的一個組合,用來求值。例如:函式呼叫、變數賦值、運算子運算等等。 表示式的解析難點有二:一是運算子的優先順序問題,二是如何將表示式編譯成目的碼。我們就來逐一說明。 手把手教你構建 C 語言編譯器系列共有10個部分: 運算子的優先順

手把手構建 C 語言編譯器2

本章是“手把手教你構建 C 語言編譯器”系列的第三篇,本章我們要構建一臺虛擬的電腦,設計我們自己的指令集,執行我們的指令集,說得通俗一點就是自己實現一套匯編語言。它們將作為我們的編譯器最終輸出的目的碼。 手把手教你構建 C 語言編譯器系列共有10個部分: #計算機的內部工作原理 計算機中有三個基本部件需要

手把手構建 C 語言編譯器5

本章中我們用 EBNF 來大致描述我們實現的 C 語言的文法,並實現其中解析變數定義部分。 由於語法分析本身比較複雜,所以我們將它拆分成 3 個部分進行講解,分別是:變數定義、函式定義、表示式。 手把手教你構建 C 語言編譯器系列共有10個部分: EBNF 表示 EBNF 是對前一章提到的 BNF 的擴充

手把手構建 C 語言編譯器9

恭喜你完成了自己的 C 語言編譯器,本章中我們發一發牢騷,說一說編寫編譯器值得注意的一些問題;編寫編譯器時遇到的一些難題。 手把手教你構建 C 語言編譯器系列共有10個部分: 虛擬機器與目的碼 整個系列的一開始,我們就著手虛擬機器的實現。不知道你是否有同感,這部分對於整個編譯器的編寫其實是十分重要的。我認

手把手構建 C 語言編譯器1

本章是“手把手教你構建 C 語言編譯器”系列的第二篇,我們要從整體上講解如何設計我們的 C 語言編譯器。 手把手教你構建 C 語言編譯器系列共有10個部分: 首先要說明的是,雖然標題是編譯器,但實際上我們構建的是 C 語言的直譯器,這意味著我們可以像執行指令碼一樣去執行 C 語言的原始碼檔案。這麼做的理由

手把手構建 C 語言編譯器0

“手把手教你構建 C 語言編譯器” 這一系列教程將帶你從頭編寫一個 C 語言的編譯器。希望通過這個系列,我們能對編譯器的構建有一定的瞭解,同時,我們也將構建出一個能用的 C 語言編譯器,儘管有許多語法並不支援。 手把手教你構建 C 語言編譯器系列共有10個部分: 在開始進入正題之前,本篇是一些閒聊,談談這

手把手構建 C 語言編譯器3

本章我們要講解如何構建詞法分析器。 手把手教你構建 C 語言編譯器系列共有10個部分: 什麼是詞法分析器 簡而言之,詞法分析器用於對原始碼字串做預處理,以減少語法分析器的複雜程度。 詞法分析器以原始碼字串為輸入,輸出為標記流(token stream),即一連串的標記,每個標記通常包括: (token,