インライン関数まとめ
インライン関数についてややこしいところをまとめておきます。
結論から言うと、常に static inline
を使え、ってことになります。
結果だけ知りたい人は以下は読む必要なし。
最初に
inline
キーワードは関数callを高速化せよ、という指定であって、inline
を付けたからといって、本当にインライン展開されるかはわかりません。
実際のところ、最適化オプションをつけていない場合、inline
を付けても、一切インライン展開してくれません。
最適化オプションによらず、インライン展開を強制させるには __attribute__((always_inline))
をつけるというやり方があります。
-O1
オプションをつけた場合、明示的に inline
がついている場合はなるべくインライン展開しようとするが、inline
指定がないものについては インライン展開しないようだ。
-O2
オプションをつけた場合、inline
指定がなくても、インライン展開をする場合がある。static 関数や、非常に短い関数など。
続いて重要なのは、インライン展開された結果、展開された関数の定義コードが残るのか、消えるのかという点について。 ここを間違えると、リンク時に関数の多重定義でエラーになったり、逆に関数が未定義でエラーになったりします。
これについては gcc のマニュアルに説明があるので、ほとんどここに書いてあるとおりなのですが。
-std=
の指定によって挙動が違ってくる部分もあるのだが、まず全体通して共通している部分から。
ケース1
static inline int inc(int *a)
{
return (*a)++;
}
のように static 関数に inline
がついた場合。
この場合、(static inline
の方が static よりもスピード重視であることを除き)基本的に inline
を付けなかった時と同じ扱いになる。
どういうことかと言うと、すべての関数 call がインライン展開されたときは関数定義は残らない。 インライン展開されないcall があったり、関数のアドレス参照があったりすると関数の定義は残ります。
ちなみに、以下のように inline
をつけずに、__attribute__((always_inline))
をつけ、最適化オプションなしでコンパイルした場合、インライン展開はされたが、関数定義は残った。普通はこういう使い方はしないだろうが。。
static int __attribute__((always_inline)) inc(int *a)
{
return (*a)++;
}
ケース2
extern int inc(int *a);
inline int inc(int *a)
{
return (*a)++;
}
のように、 inline
なしで宣言された後、 inline
付きで定義された場合。
(この例では、宣言の直後に定義を書いているが、普通は宣言の部分はヘッダーに書くはずです。)
この時も inline
がなかった時と同じような挙動になる。
すべての関数callがインライン展開されたとしても、必ず関数定義は残る。
ここからは -std=
の指定によって挙動が異なる。
ケース3
inline int inc(int *a)
{
return (*a)++;
}
のように宣言はなくて、 inline
付きで定義された場合。
もしくは、宣言と定義ともに inline
が付いている時。
-std=gnu90
(gnu89) または -fgnu89-inline
を指定しているときは
すべての関数callがインライン展開されたとしても、他のファイルからの関数callに備えて、関数定義は残ります。
一方、-std=gnu99
または -std=c99
が指定された時は、全く逆で、この関数定義はインライン展開用にしか使われません。そのため、inline
指定の関数定義は必ず消える。
最適化オプションをつけていないときはインライン展開されていないにも関わらず、関数定義は消えてしまう。
ちなみに、-std=c90
のときは inline
キーワードを認識しません。
これを書いている時点 (gcc version 4.7) では -std=gnu90
がデフォルトです。
将来的には -std=gnu99
がデフォルトになるそうですが。
ケース4
extern inline int inc(int *a)
{
return (*a)++;
}
のように extern
と inline
が同時に指定された場合。
-std=gnu90
(gnu89) または -fgnu89-inline
の時は、この関数定義はインライン展開のみに使われ、関数定義は残らない。
-std=gnu99
または -std=c99
の時は、関数定義が必ず残る。
まとめ
デフォルト (-std=gnu90
)では、以下のような挙動になる。
-
static inline
すべての関数call がインライン展開されると関数定義が消える
-
inline
なしの宣言 +inline
付きの定義関数定義は必ず残る
-
inline
関数定義は必ず残る
-
extern inline
関数定義は必ず消える。
ただし、 -std=gnu99
または -std=c99
オプションをつけると 3 と 4 の扱いが逆転します。
以上のことからわかるように、ヘッダーファイルに書いてマクロ関数の代わりにできるのは 1 と 4 です。 2 と 3 はヘッダーに書いてしまうと、(そのヘッダーが複数のファイルからインクルードされていると)multiple definition でエラーになります。
1 をヘッダーに記述し、複数のソースからインクルードさせる場合、 -O1 以上の最適化オプションか、__attribute__((always_inline))
が必要。
そうしないと、やはり関数の multiple definition でリンクエラーになる。
4 の使い方としてはヘッダーファイルにインライン展開用のコードを書いておき、一つのCソースに、インライン展開されなかった時用の定義を(extern inline を外して)書いておく。
ただし、記述が重複する上に、 -std=
オプションによって挙動が逆転するので使いにくいです。
以上のことから、 1 が最も使い勝手がよく、インライン関数は static inline
を使え、ということになります。
Linux Kernelのコードをみてもほとんど static inline
になっています。
たまに extern inline
がありますが。。。
blog comments powered by Disqus