C編譯器入門~想懂低階系統從自幹編譯器開始~
  • 譯者序
  • 前言
    • 符號與規範
    • 本書的開發環境
    • 關於作者
    • 結束前言之前
  • 機械語言與組譯器
    • CPU 與記憶體
    • 什麼是組譯器
    • C程式和所對應的組合語言
      • 簡單的範例
      • 包含呼叫函式的範例
    • 本章小結
  • 創造計算機等級的語言
    • 第1步:創造能編譯1個整數的語言
    • 第2步:製作可以算加減法的編譯器
    • 第3步:加入標記解析器(tokenizer)
    • 第4步:改良錯誤訊息
    • 文法的記法與遞迴下降分析法
      • 將文法結構表示為樹(tree)
      • 以生成規則定義文法
      • 以 BNF 描述生成規則
      • 簡單的生成規則
      • 以生成規則描述運算子的優先順序
      • 包含遞迴的生成規則
      • 遞迴下降語法分析
    • 堆疊機
      • 堆疊機的概念
      • 編譯成堆疊機指令
      • 以x86-64實作堆疊機的方法
    • 第5步:製作可進行四則運算的編譯器
    • 第6步:單項加與單項減
    • 第7步:比較運算子
      • 修改標記解析器
      • 新的文法
      • 產生組合語言指令
  • 分離編譯與連結
    • 分離編譯
      • 分離編譯與其必要性
      • 標頭檔的必要性與其內容
      • 連結錯誤
      • 全域變數的宣告與定義
    • 第8步:分割檔案與修改 Makefile
      • 分割檔案
      • 修改 Makefile
  • 函式與區域變數
    • 第9步:1個字的區域變數
      • 堆疊上的變數空間
      • 修改標記解析器
      • 修改分析器
      • 左邊值與右邊值
      • 從任意的記憶體位址取得其值
      • 修改指令產生器
      • 修改主函式
    • 第10步:複數文字的區域變數
    • 第11步:return
    • 1973年的C編譯器
Powered by GitBook
On this page

Was this helpful?

  1. 創造計算機等級的語言

第6步:單項加與單項減

Previous第5步:製作可進行四則運算的編譯器Next第7步:比較運算子

Last updated 5 years ago

Was this helpful?

減法運算的-運算子,不只可以像在5-3一樣寫在2項中間,也可以像-3一樣寫在單項前面。同理,+運算子也可以像+3這樣用在沒有左項的時候。像這樣只取單項的運算子稱為「單項運算子」(unary operator)。相對的,取2項的運算子就稱為「2項運算子」(binary operator)。

C語言中,除了+和-以外,也有取得指標位址的&和提取指標(dereference)的*這些單項運算子。不過這一步我們只會實作+和-這兩個而已。

單項+和單項-雖然和2項的+和-是一樣的符號,但是定義不同。2項的-的定義是從左邊減去右邊,但是單項根本沒有左邊,照搬2項-的定義的話會無法適用。C語言的單項-是定義為把右邊的正負號反轉的運算。而單項+則是把右邊直接傳回的運算子,這個運算子不是必要的,是和單項-成對存在的贈品。

我們可以合理想像,像+和-這樣,有單項和2項相似但不同定義的同名運算子有很多個。是單項還是2項則需要看前後文脈絡來區分。底下是包含單項+/-的新文法:

expr  = mul ("+" mul | "-" mul)*
mul   = unary ("*" unary | "/" unary)*
unary = ("+" | "-")? term
term  = num | "(" expr ")"

上述文法追加了unary這個非終端符號,mul不是用上term而是改為unary。X?是代表選擇性的,也就是說,X會出現0次或1次的 EBNF 語法。unary = ("+" | "-")? term這條規則的意思,是表示unary這個非終端符號,不論有或沒有1個+/-符號其後都會接上term。

我們來驗證-3或-(3+5)、-3*+5這類式子可以和新的文法對應。底下是-3*+5的語法樹:

我們來依照這個文法修改分析器。照慣例,只要把文法照搬成函式的呼叫我們的分析器應該就改好了。分析unary的函式如下所示:

Node *unary() {
  if (consume('+'))
    return term();
  if (consume('-'))
    return new_node(ND_SUB, new_node_num(0), term());
  return term();
}

在這個階段,分析器把+x用x替換、-x用0-x替換。所以在這一步我們不用修改指令產生器。

加上幾條測試、和加上單項+/-的程式碼一起 commit 後,這一步就做完了。寫測試的時候,要注意測試的結果要落在0~255之間。在這一步,請利用像-10+20這樣,用上了單項-但是整體的結果是正數的算式。

小知識:單項加減和文法的好壞

單項+運算子在原始的C編譯器裡並不存在,是在1989年 ANSI(美國國家標準協會)在制定C語言標準時,官方才加上去的。因為有單項-,確實有單項+會提升對稱性,但實際上單項+並沒有什麼用處。

同時,在文法裡加上單項+有其副作用。不習慣C語言的人可能會誤把+=運算子錯寫成i =+ 3。如果沒有單項+的話會回報語法錯誤,但是因為有單項+所以這會被解釋為i = +3,編譯器會以為這是正確的代入式默默地接受。像這樣,在擴充文法時發生沒想到的副作用是在設計語言時很常發生的事。

ANSI 制定C語言標準的小組,應該是了解上述問題的前提之下決定加上單項+的,不過讀者們會怎麼想呢?如果你是C語言標準小組的一員,你會贊成還是反對呢?

參考實作:

bb5fe99dbad62c95
-3*+5的語法樹