# 淺談 `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))
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up