とあるエンジニアの備忘log
2012年8月4日土曜日
インライン関数まとめ
インライン関数についてややこしいところをまとめておきます。 結論から言うと、常に `static inline` を使え、ってことになります。 結果だけ知りたい人は以下は読む必要なし。 ### 最初に ### `inline` キーワードは関数callを高速化せよ、という指定であって、`inline` を付けたからといって、本当にインライン展開されるかはわかりません。 実際のところ、最適化オプションをつけていない場合、`inline` を付けても、一切インライン展開してくれません。 最適化オプションによらず、インライン展開を強制させるには `__attribute__((always_inline))` をつけるというやり方があります。 `-O1` オプションをつけた場合、明示的に `inline` がついている場合はなるべくインライン展開しようとするが、`inline`指定がないものについては インライン展開しないようだ。 `-O2` オプションをつけた場合、`inline`指定がなくても、インライン展開をする場合がある。static 関数や、非常に短い関数など。 続いて重要なのは、インライン展開された結果、展開された関数の定義コードが残るのか、消えるのかという点について。 ここを間違えると、リンク時に関数の多重定義でエラーになったり、逆に関数が未定義でエラーになったりします。 これについては [gcc のマニュアル](http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/Inline.html)に説明があるので、ほとんどここに書いてあるとおりなのですが。 `-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`)では、以下のような挙動になる。 1. `static inline` すべての関数call がインライン展開されると関数定義が消える 2. `inline` なしの宣言 + `inline` 付きの定義 関数定義は必ず残る 3. `inline` 関数定義は必ず残る 4. `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` がありますが。。。
0 件のコメント:
コメントを投稿
次の投稿
前の投稿
ホーム
登録:
コメントの投稿 (Atom)
0 件のコメント:
コメントを投稿