C言語のインライン関数について
GCCや多くのコンパイラーは,C言語の標準(C99)にインライン関数が取り込まれるずっと前からインライン関数をサポートしていた。そのためかインライン関数の定義は様々で,同じ書式でも各コンパイラで挙動が異なる場合がある。
今回は,インライン関数の定義をまとめ。それぞれ出力されるアセンブラコードを比べてみる。
概要
先頭に inline という言葉を付けて関数を宣言すると,コンパイラーはそれをヒントにコードをインライン化――関数のコードを呼出し元に展開する。これにより,関数呼び出しのオーバーヘッドが取り除かれ実行が早くなる。
inline はコンパイラに対するヒントであって指示ではないから,様々な理由からヒントが無視され,インライン化されず実際の関数として出力される場合がある。例えば,その関数のアドレスが参照される場合や,関数定義内の再帰呼出しなどはインライ化できないし,コンパイラーの最適化を無効にした場合インライン化は行われない。
GCCのインライン関数
GCCでは inline,static inline,そして extern inline が定義されている。
static inline
関数を static inline と宣言すると,関数がすべてインライン化されれば,その関数自体は出力されない。
次のようなサンプルコードを用意した:
static inline void outb(unsigned short port, unsigned char val) { __asm__ __volatile__ ("out dx, al" : : "d" (port), "a" (val)); } int fnc() { outb(0x20, 0x11); }
これを次のようにコンパイルした:
gcc -O2 -fno-ident -finhibit-size-directive -fomit-frame-pointer -fcall-used-ebx -masm=intel -S -c main.c
出力されたコードは次の通りである:
.file "main.c" .intel_syntax noprefix .text .p2align 4,,15 .globl fnc .type fnc, @function fnc: mov eax, 17 mov edx, 32 #APP # 2 "main.c" 1 out dx, al # 0 "" 2 #NO_APP ret .section .note.GNU-stack,"",@progbits
outb()がインライン化され,関数がfnc()ひとつしか残ってないことが分かる。
inline
関数を inline と宣言すると,static でないため,コンパイラは他のソースファイルからの呼出しがあることを想定しなければならない。そのため,関数がすべてインライン化されても,常に関数自体は出力される。
出力されたコードは次の通りである:
.file "main.c" .intel_syntax noprefix .text .p2align 4,,15 .globl outb .type outb, @function outb: movzx edx, WORD PTR [esp+4] movzx eax, BYTE PTR [esp+8] #APP # 2 "main.c" 1 out dx, al # 0 "" 2 #NO_APP ret .p2align 4,,15 .globl fnc .type fnc, @function fnc: mov eax, 17 mov edx, 32 #APP # 2 "main.c" 1 out dx, al # 0 "" 2 #NO_APP ret .section .note.GNU-stack,"",@progbits
outb()はインライン化されているが,関数outb()が出力されている。
extern inline
関数を extern inline と宣言すると,外部参照になるため,常に関数自体は出力されない。
出力されたコードは次の通りである:
.file "main.c" .intel_syntax noprefix .text .p2align 4,,15 .globl fnc .type fnc, @function fnc: mov eax, 17 mov edx, 32 #APP # 2 "main.c" 1 out dx, al # 0 "" 2 #NO_APP ret .section .note.GNU-stack,"",@progbits
ここでは,static inlineと同じコードが出力された。
もし,関数がインライン化されない場合は,外部参照になるため,次のような関数本体のコピーを外部ファイルに用意する必要がある:
void outb(unsigned short port, unsigned char val) { __asm__ __volatile__ ("out dx, al" : : "d" (port), "a" (val)); }
C99のインライン関数
C99ではGCCの三つのインライン関数が取り込まれた。
マクロより static inline を推奨
型の安全性があり、フォーマットの制限も無いため static inline 関数はマクロよりもずっと推奨されている。