C言語のinline

C/C++のinlineで間違いやすい3つのポイントがある。

1つは、GCCは3種類の異なるinline仕様を使い分けているという点である。3種類とは、「C++のinline」「C90用のGCC拡張inline」「C99以降のinline」である。

2つ目は、inlineを使っても、コンパイラが必ずインライン化を行うとは限らないという点である。

3つ目は、inlineを使うときは、プログラマは必ず、コンパイラがインライン化を行えるように特定の配慮をしなければいけないという点である。

つまり、inline関数は、「実体がどこにあるか」「inline化のための情報が足りているか」という2つの状態を同時に制御する必要がある。この細かい扱いの違いがバージョンにより異なるということになる。

以下バージョンごとの解説。おそらく歴史的な導入順序とは逆になっている。

C99以降のinline

C99以降のinlineでは、次の2つの条件を独立に満足させればよい。

  1. inlineに関係なく、実体がちょうど1つ存在する。
  2. inline関数が宣言されている全ての翻訳単位で、対応する「インライン化用の定義」が存在している。 (翻訳単位: 拡張子cのファイル1個につき翻訳単位1個と思っておけばほぼ問題ない)

これを判定するには次の表を用いればよい。

inline宣言の例 定義(条件2) 実体(条件1)
inline void f(void); × void f(void);
extern inline void f(void); × void f(void);
static inline void f(void); × static void f(void);
inline void f() {} void f(void);
extern inline void f() {} void f() {}
static inline void f() {} static void f() {}

ここで「定義」は、インライン化用の定義が存在するかどうか?の意味である。「実体」はインライン化しない場合のための実体が出力されるかどうか?の意味である。

気をつけるべき点は、関数定義で inline だけを指定した場合である。よく表を見て意味を確認してほしい。

C90のGNU拡張inline

extern がほぼ正反対の意味で使われているのが最も大きな違いである。つまり、 inline とだけ書くと外部リンクされる定義が与えられ、 extern inline だとインライン化専用になる。

C++のinline

C++のinline関数は、複数の翻訳単位が実体を提供してもよい。このあたりはリンカが頑張る。そのため、 inlineextern inline の区別はなく、いずれの場合も実体が生成される。その他、宣言していてもodr-usedでない場合の制限が緩い、inline関数がstatic変数を持っていてもよいなどいくつかの違いがある。

互換性を高めるためには

GCC拡張の説明にも、互換性を高める方法が書いてある。

内部リンケージするなら、単に static inline を常に使う。

外部リンケージするなら、 inline のついていない外部リンケージの宣言と、 inline のみついた定義を、この順に書く。