c語言if語句是如何變成彙編程式碼的?
- 2021 年 11 月 23 日
- 筆記
1. 要編譯的測試程式碼:
int a; int b = 3; int main(void) { if (3) a = 4; else b = 5; }
2. 詞法分析
詞法分析將c源程式碼解析成一個個的token。
關鍵的,將if兩個字元解析成一個if token,後續語法分析的輸入就從兩個字元減少為1個token,減小了語法分析的難度。
3. 語法分析
if (equal(tok, "if")) { Node *node = new_node(ND_IF, tok); tok = skip(tok->next, "("); node->cond = expr(&tok, tok); tok = skip(tok, ")"); node->then = stmt(&tok, tok); if (equal(tok, "else")) node->els = stmt(&tok, tok->next); *rest = tok; return node; }
如果當前處理的token是if,則
3.1創建新的類型為ND_IF的node。
3.2跳過if後面的”(“。
3.3調用expr函數解析if語句()中的表達式,並將解析結果存儲在node->cond。
3.4跳過「)」。
3.5調用stmt處理then語句塊中的語句,這裡是處理”a = 4;”,將解析結果存儲在node->then。
3.6如果if語句還有else部分,則調用stmt處理else語句塊中的語句,這裡是處理”b = 5;”,將解析結果存儲在node->els。
3.7node->cond,node->then,node->els都為node節點。
4. 程式碼生成
switch (node->kind) { case ND_IF: { int c = count(); gen_expr(node->cond); cmp_zero(node->cond->ty); println(" je .L.else.%d", c); gen_stmt(node->then); println(" jmp .L.end.%d", c); println(".L.else.%d:", c); if (node->els) gen_stmt(node->els); println(".L.end.%d:", c); return; } ...
如果當前處理的node節點類型為ND_IF,則
4.1gen_expr
這個函數處理if語句的條件部分,這裡是處理3。判斷node節點為NUM,會生成彙編語句”mov rax, 3″,將3載入rax暫存器。
4.2cmp_zero
cmp_zero會生成彙編語句”cmp eax, 0″,比較3和0。
4.3println(” je .L.else.%d”, c);
該語句會生成彙編程式碼” je .L.else.1″,當上條比較語句中eax為0時會執行跳轉,跳轉到else分支運行。這裡由於eax為3,所以不跳轉。
4.4gen_stmt(node->then);
這條語句會將then分支中的語句解析為彙編源碼,這裡是”a = 4;”,這條語句是表達式語句,所以會調用gen_expr函數。
4.4.1gen_expr
“lea rax, a”,將a的地址載入rax暫存器中。
“push rax”,將rax入棧。
“mov rax, 4”,將4載入rax暫存器中。
“pop rdi”,將變數a的地址載入rdi暫存器。
“mov [rdi], eax”,將4寫入變數a。
4.5println(” jmp .L.end.%d”, c);
執行完then分支程式碼後跳轉到下一條語句處執行。
4.6println(“.L.else.%d:”, c);
插入一條標籤,表示else分支程式碼的開始,如果if語句條件為0會跳轉到這。
4.7gen_stmt(node->els);
生成else分支程式碼,處理”b = 5;”。
“lea rax, b”,將變數b的地址載入rax暫存器。
“push rax”,將rax暫存器入棧。
“mov rax, 5”,將5載入rax暫存器。
“pop rdi”,將b的地址載入rdi暫存器。
“mov [rdi], eax”,將5寫入變數b中。
4.8println(“.L.end.%d:”, c);
插入一條標籤,表示if語句的結束,then分支語句執行完成後跳轉到這裡。


