# 包含呼叫函式的範例

我們來看一個比較複雜的範例，看看有呼叫函式的程式會被編成什麼樣的組合語言指令。

呼叫函式和一般的跳躍不同，在呼叫結束後必須回到原本呼叫的地方，原本執行中的位址被叫做「回傳位址」（return address）。如果說呼叫只會發生一次的話，隨便找一個暫存器存回傳位址就好了；但是函式呼叫可以一層一層呼叫下去，所以必須把回傳位址存在記憶體裡。實務上，回傳位址被存在記憶體中的堆疊（stack）裡。

堆疊，被實作成只能使用堆疊空間最上方位址所存的一個變數。而這個紀錄堆疊最上方的紀錄空間被稱為「堆疊指標」（stack pointer）。x86-64 中，為了方便寫呼叫函式的程式，提供了堆疊指標專用的暫存器，和使用這個暫存器的指令。往堆疊上堆資料的操作是「push」，而取出堆疊資料的操作是「pop」。

接下來我們來看看操作堆疊的範例。試想以下的C程式碼：

```c
int plus(int x, int y) {
  return x + y;
}

int main() {
  return plus(3, 4);
}
```

而底下是與這個C程式碼對應的組合語言指令：

```
.intel_syntax noprefix
.global plus, main

plus:
        add rsi, rdi
        mov rax, rsi
        ret

main:
        mov rdi, 3
        mov rsi, 4
        call plus
        ret
```

第一行是指定組合語言文法的指令。由`.global`開始的第二行，是指定`main`和`plus`這兩個可見於程式全體（program scope），非檔案範圍（file scope）的函式的組合語言指令。（譯註：有關 program scope 和 file scope，請參考C語言的相關資料。）現階段可以暫時忽略沒有關係。

首先來看`main`。C程式看起來就是在`main`呼叫了`plus`函式；在組合語言部分，默認第一引數（argument）放在 RDI 暫存器、第二引數放在 RSI 暫存器，`main`的最初兩行便是設定這兩個暫存器的值。

`call`就是呼叫函式的指令。具體來說`call`做了下列兩件事情：

* 把`call`的下一行指令的位址 push 進堆疊中
* 跳到`call`的引數所給的位址

於是，`call`實行時，CPU 便會開始執行`plus`。

接下來看`plus`。`plus`函式包含有三個指令。

`add`是加法的指令。在這裡是把 RSI 和 RDI 兩個暫存器內的值相加，再寫回 RSI 暫存器中。x86-64 的整數運算指令通常都只接受兩個暫存器，所以必須要把運算結果寫回其中一個暫存器中。

函式的回傳值則被寫進 RAX 暫存器。由於我們想要的是加法的結果，所以必須把 RSI 暫存器的值複製到 RAX 暫存器，這邊用來複製的指令是`mov`指令。`mov`是 move 的省略，但實際上並不是移動而是複製資料的指令。

`plus`函式的最後，執行`ret`指令從函式中回傳。`ret`指令具體來說做的是下列兩件事：

* 從堆疊中 pop 一個位址出來
* 跳到該位址

也就是說，`ret`指令是跳回執行`call`的地方，並從該處開始繼續執行的指令。`call`和`ret`是成對設計的指令。

從`plus`傳回之後就是`main`裡的`ret`指令。從C程式看，`main`是把`plus`的回傳值直接傳回去。在這裡 RAX 裡放的是`plus`的回傳值，所以這裡`main`直接回傳，RAX 裡的值也就成了main的回傳值了。
