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の三つのインライン関数が取り込まれた。

  • inline : GCCの extern inline と等価
  • extern inline : GCCの inline と等価
  • static inline : GCCの static inline と等価

マクロより static inline を推奨

型の安全性があり、フォーマットの制限も無いため static inline 関数はマクロよりもずっと推奨されている。