# 淺談 `C` 的 `inline`
> 若您發現用詞不精確或語句不通順,歡迎直接修改。
## 研究動機
筆者同事有次問筆者為什麼以下程式會有連結時期的錯誤,出現 `fib.c:(.text+0xf): undefined reference to fib` 的錯誤訊息。為什麼在遞迴函式中加了 `inline` 會讓連結器找不到函式呢?
``` cpp
inline int fib(int n) {
if (n < 2)
return 1;
return fib(n - 1) + fib(n - 2);
}
int main() {
fib(10);
}
```
筆者希望可以藉著這個機會,閱讀C語言規格並記下歷程,以及試圖去理解規格所構思。
## 簡介
`inline` 修飾詞主要的功能是提醒編譯器這個函式值得被 `inline`,但僅是提醒而已,最後實作與否取決於編譯器的實作。
## 規格書裡面的 `inline`
問題主要來自於 [C99 規格書](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf)的原因是出自於 $6.7.4.6 的最後一行:
::: info
For a function with external linkage, the following restrictions apply: (ignored ...)
It is unspecified whether a call to the function uses the inline definition or the external definition.
:::
在 `fib` 是 `external linkage` 的情況下,規格書並沒有規範 `main` 呼叫的 `fib` 是 `inline definition` 還是 `external definition`。而此案例中 `main` 並所 call 的 `fib` 是檔案外的 `fib`。
## 解決方案
第一個方案當然是不要在遞迴函數前加 `inline`,畢竟遞迴函數無法被 `inline`,有機會不會被編譯器選擇為 `callee`。
第一個方案其實有些狹隘,畢竟不是遞迴的函式也是有機會不被 `inline`。而這裡提供的第二個方案就是多寫一個實作在別的 `.c` 檔,使連結時期可以順利。
第三個方案是加上 `static`,強迫該函式轉為 `internal linkage`。因此即使編譯器判斷 `inline` 不划算,也不會選擇呼叫外部定義的函式。但值得注意的是,如果這個函式定義會在其他`translation unit` 使用的話, `internal linkage` 會讓其他 `translation unit` 無法使用。
第四個方案是加上 `extern`,令該函式轉為 `external definition`,因此該函式會在編譯結束後會被留下。但缺點是如果其他 `translation unit` 也有自己的實作且非 `inline` ,則連結時期會有重複定義的錯誤。
## 設計原由
同樣來自 $6.7.4.6,規格書解釋這樣的規範可以在同個 `translation unit` 中,提供實作的機會取代外在的函式定義。
::: info
An inline definition provides an alternative to an external definition, which a translator may use to implement any call to the function in the same translation unit.
:::
筆者認為這樣的設計有以下好處:
- 在不開啟 `LTO` 的情境,該 `trasnlation unit` 所呼叫的函式是外部的定義,在函式無法得知定義的情況,編譯器無法實作 `inline`。 因此我們可以利用 `inline` 提供實作。如果你願意相信編譯器 `inline` 的判斷,則在編譯器判斷不適合 `inline` 的場合,編譯器會放棄你提供的 `inline` 實作版本,選用呼叫外部定義的實作。
- 配合 `static` 加在 `inline` 上面可以放在 `.h` 檔,減少實作的程式碼。使用 `inline` 比起巨集,也有更好的型別檢查機制。(範例: [list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h))