標籤:

既然編譯器可以判斷一個函數是否適合 inline,那還有必要自己加 inline 關鍵字嗎?

我指的是 C++ 標準的 inline,不是編譯器自帶的那種可以指定必須內聯的 inline ,

還有編譯器有可能對沒有加 inline 關鍵字的函數內聯嗎?


inline 絕對是C++里最讓人混淆的關鍵詞之一了(比static還過分)。

============== Update 30 Nov 2016

看其他評論里有提到static 的。個人評價一下 static + inline 一起:那就是把死人往活里搞,活人往死里搞的趕腳,坑之深簡直不忍直視。先上追加的3個結論;後面有代碼,有耐心的小夥伴們拿回去自己試。

3. 謹慎使用 static:如果只是想把函數定義寫在頭文件中,用 inline,不要用static。static 和 inline 不一樣:

  • static 的函數是 internal linkage。不同編譯單元可以有同名的static 函數,但該函數只對 對應的編譯單元 可見。如果同一定義的 static 函數,被不同編譯單元調用,每個編譯單元有自己單獨的一份拷貝,且此拷貝只對 對應的編譯單元 可見。
  • inline 的函數是 external linkage,如果被不同編譯單元調用,每個編譯單元引用/鏈接的是同一函數,同一定義。

  • 上面的不同直接導致:如果函數內有 static 變數,對inline 函數,此變數對不同編譯單元是共享的(Meyer"s Singleton);對於static 函數,此變數不是共享的。看後面的代碼就明白區別了。

4. static inline 函數,跟 static 函數單獨沒有差別,所以沒有意義,只會混淆視聽。

5. inline 函數的定義不一定要跟聲明放在一個頭文件裡面:定義可以放在一個單獨的頭文件 .hxx 中,裡面需要給函數定義前加上 inline 關鍵字,原因看下面第 2.點;然後聲明 放在另一個頭文件 .hh 中,此文件include 上一個 .hxx。這種用法 boost里很常見:優點1. 實現跟API 分離,encapsulation。優點2. 可以解決 有關inline 函數的 循環調用問題:這個不展開說了,看一個這個文章就懂了:Headers and Includes: Why and How 第 7 章,function inlining。

Reference: inline specifier

============== 原答案30 Nov 2016

1. 不要再把 inline 和編譯器優化掛上關係了,太誤導人。編譯器不傻,inline is barely a request。你不加inline,小函數在開O3時,編譯器也會自動給你優化了。看到inline時,應該首先想到其他用意,在考慮編譯器優化。

2. inline最大的用處是:非template 函數,成員或非成員,把定義放在頭文件中,定義前不加inline ,如果頭文件被多個translation unit(cpp文件)引用,ODR會報錯multiple definition。

============== static / inline 代碼

a.hh

#ifndef A_HH
# define A_HH

# include &

namespace static_test
{
static int static_value() // (!*!) Or change this to inline
{
static int value = -1;
return value;
}

namespace A
{
void set_value(int val);
void print_value();
}
}

#endif

a. cc

# include "a.hh"

namespace static_test
{
namespace A
{
void set_value(int val)
{
auto value = static_value();
value = val;
}

void print_value()
{
std::cout &<&< static_value() &<&< " "; } } }

b.hh:

#ifndef B_HH
# define B_HH

# include &

namespace static_test
{
namespace B
{
void set_value(int val);
void print_value();
};
}

#endif

b.cc:

# include "a.hh"
# include "b.hh"

namespace static_test
{
namespace B
{
void set_value(int val)
{
auto value = static_value();
value = val;
}

void print_value()
{
std::cout &<&< static_value() &<&< " "; } } }

main. cc

# include "a.hh"
# include "b.hh"

int main()
{
static_test::A::set_value(42);

static_test::A::print_value();
static_test::B::print_value();

static_test::B::set_value(37);

static_test::A::print_value();
static_test::B::print_value();

return 0;
}

  • a.hh 中標註 (!*!) 的那行,如果是inline,輸出:42,42,37,37。value 在整個程序中是個Singleton
  • 如果是 static,輸出:42,-1,42,37。value 在不同編譯單元是不同的拷貝,即使它被標註 static


一般來說沒必要。作為成員函數來說,一行的短函數,你就實現在類定義裡面,不用寫inline。實現到cpp,也不用寫inline。沒有什麼理由讓一個成員函數既不實現在類定義里,又放在頭文件。

但是,如果是非成員函數,要實現在頭文件里,就幾乎必須都得inline。否則被多個編譯單元include的話,會掛。


有必要,對於全局函數,inline會屏蔽掉ODR檢查,並且要求編譯器讓所有編譯單元使用同一份定義(地址),這樣你才能把定義了inline函數的頭文件到處include而在編譯和鏈接的時候不會遇到重定義錯誤

所以你在有些庫裡面會看到這樣的條件define

#if 編譯器支持constexpr
# define myconstfunc constexpr
#else
# define myconstfunc inline
#endif

inline不僅僅是字面意義的「內聯」


關鍵詞 inline 的語義可以說不是 inline 。或許它叫做 defonce 都好一點。

它保證的是頭文件里寫函數實現的話,鏈接不出錯,且最終程序里最多只有一份非內聯的函數代碼。

與其說它為內聯服務,不如說它主要為不內聯的情況擦屁股。(不想擦的話可以用 static 代替 inline)


編譯器有可能對沒有加 inline 關鍵字的函數內聯嗎?

答案是肯定的,你大可寫一個例子測試嘛,前提是編譯器在編譯的時候要看得到函數體。

既然編譯器可以判斷一個函數是否適合 inline,那還有必要自己加 inline 關鍵字嗎?

當然,由於頭文件機制下的歷史原因,這個很有必要。

一個項目往往由多個編譯單元組成,每個源文件都是一個編譯單元。編譯器的工作是一個單元一個單元順著編譯成目標文件,然後鏈接器統一把所有目標文件鏈接成可執行文件。

在編譯某個單元的時候,編譯器是看不到其他單元的函數定義的,除非你提供一個聲明,那麼編譯器就可以在函數調用處預留一個符號,等鏈接的時候來填具體的地址。當然,這是常規調用的實現方式。

頭文件會在include的地方被編譯器無腦展開,所以寫在頭文件中的函數定義會遍布各個編譯單元,鏈接的時候就會報一堆符號重定義錯誤。頭文件中的inline作用其實是提醒鏈接器,鏈接的時候保留一份函數體就行了(類體內定義的成員函數、constexpr函數、模板函數都是隱式inline)。

編譯器內聯一個函數的前提之一就是必須要知道函數的具體代碼,要麼函數定義和調用點是在同一個源文件中,要麼就把函數加上inline,定義在頭文件中並include這個頭文件,總之要保證函數代碼在編譯單元中是編譯器可見的。

頭文件中的inline作用其實主要是鏈接提示,對編譯器來說也許有一定參考價值,但往往價值不大,因為適不適合內聯編譯器往往比你更懂。作為編譯提示來說屬於沒有必要,但是作為要實現內聯卻是不可或缺的關鍵字。

沒有inline還可以依賴LTO或LTCG優化,問題是你用到的靜態庫也得打開啊。


你還有__forceinline,可以在VC++裡面用,強行inline


0. // TODO mention ODR C++17 inline variable

1. 至少 clang 會把函數是不是 inline 函數作為是否進行內聯展開的參考。當然明顯可以展開 / 明顯不能展開的情況,即使沒有參考也能自動判斷

2. 「沒有加 inline 關鍵字」和「不是 inline 函數」有差別,例如在 class/struct/union definition 內定義的函數,即使沒有 inline 關鍵字,也是 inline 函數。再如 constexpr 函數都是自動成為 inline 函數


那為什麼不把所有函數都默認加上inline呢?反正不行的話就讓編譯器自己搞。


沒必要加,是否inline完全編譯器決定的。只要函數短小,編譯器一般會自動內聯的。如果函數複雜,你即便加了inline編譯器也可能取消內聯的。


在編譯器決定要不要內聯的時候 inline關鍵字相對於一個hint 可以提高被內聯的機會


推薦閱讀:

Windows 下最佳的 C++ 開發的 IDE 是什麼?
如何定義這種二維點的小於運算?
C++ 這門語言的優點體現在哪裡?
最近看到陳碩的一本書提了一個問題,「編譯器如何處理inline函數中的static變數?」
C++怎樣讀取文件才有最快的速度?

TAG:C | 編譯器 |