c語言if語句是如何變成彙編程式碼的?

Just_4_fun發表於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.7 node->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.1 gen_expr

這個函式處理if語句的條件部分,這裡是處理3。判斷node節點為NUM,會生成彙編語句"mov     rax, 3",將3載入rax暫存器。

 

4.2 cmp_zero

cmp_zero會生成彙編語句"cmp     eax, 0",比較3和0。

 

4.3 println(" je .L.else.%d", c);

該語句會生成彙編程式碼" je .L.else.1",當上條比較語句中eax為0時會執行跳轉,跳轉到else分支執行。這裡由於eax為3,所以不跳轉。

 

4.4 gen_stmt(node->then);

這條語句會將then分支中的語句解析為彙編原始碼,這裡是"a = 4;",這條語句是表示式語句,所以會呼叫gen_expr函式。

 

4.4.1 gen_expr

"lea     rax, a",將a的地址載入rax暫存器中。

"push rax",將rax入棧。

"mov     rax, 4",將4載入rax暫存器中。

"pop     rdi",將變數a的地址載入rdi暫存器。

"mov     [rdi], eax",將4寫入變數a。

 

4.5 println(" jmp .L.end.%d", c);

執行完then分支程式碼後跳轉到下一條語句處執行。

 

4.6 println(".L.else.%d:", c);

插入一條標籤,表示else分支程式碼的開始,如果if語句條件為0會跳轉到這。

 

4.7 gen_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.8 println(".L.end.%d:", c);

插入一條標籤,表示if語句的結束,then分支語句執行完成後跳轉到這裡。

 

 

 

 

相關文章