[C] inline関数再び

2018年4月2日月曜日

C

インライン展開されない場合の挙動

以前、[C] ヘッダファイルで、関数マクロではなくstatic inline関数を使うでstatic inlineにすれば、関数名の競合が起こらないという記事を書いた。

この方法だと安全ではあるけど、インライン展開は必ずされるわけではないので、コンパイル時にインライン展開されなかった場合、各コンパイル単位で関数の実体が作られてしまう。
そうすると、ソースファイルが多い場合、バイナリファイルのサイズに影響を及ぼしてくる。

static inline

実際にこの状況を確認してみよう。
サンプルプログラムは以下の通り。

inline.h
static inline int add(int x, int y) { return x + y; }
a.c
#include "inline.h"

int func_a(int x, int y) { return add(x, y); }
b.c
#include "inline.h"

int func_b(int x, int y) { return add(x, y); }
main.c
extern int func_a(int x, int y);
extern int func_b(int x, int y);

int main(void) {
    return func_a(1, 2) + func_b(3, 4);
}

インライン展開される場合

まずはインライン展開される場合。gccのオプションに -O1 をつけて。

$ gcc -std=c99 -O1 -o main_O1 a.c b.c main.c
$ readelf -s main_O1 | grep add

add関数はシンボルが作成されていない。

インライン展開されない場合

次にインライン展開されない場合。gccのオプションに -O0 をつけよう。

$ gcc -std=c99 -O0 -o main_O0 a.c b.c main.c
$ readelf -s main_O0 | grep add
    47: 00000000004004ed    20 FUNC    LOCAL  DEFAULT   15 add
    49: 0000000000400520    20 FUNC    LOCAL  DEFAULT   15 add

add関数はLOCALで2つシンボルが作成されていることがわかる。

staticを外す

じゃあ、インライン展開されなかった場合の実体を1ヶ所だけにしたい場合はどうすればいいだろう。
関数名がグローバルになって構わないという前提が必要だけど。

まずは単純にインライン関数からstaticを取ってみる。

inline.h
inline int add(int x, int y) { return x + y; }

-O1 をつけて、実際にインライン展開された場合は問題ない。

$ gcc -std=c99 -O1 -o main_O1 a.c b.c main.c
$ readelf -s main_O1 | grep add

でも、-O0 でインライン展開しないようにすると、エラーになってしまう。

$ gcc -std=c99 -O0 -o main_O0 a.c b.c main.c
/tmp/cc70Gu7Y.o: 関数 `func_a' 内:
a.c:(.text+0x19): `add' に対する定義されていない参照です
/tmp/ccrvR1kz.o: 関数 `func_b' 内:
b.c:(.text+0x19): `add' に対する定義されていない参照です
collect2: error: ld returned 1 exit status

inline, extern

ここで最初に紹介した方法が生きてくる。
既存のソースコードに追加してもいいけど、今回はインライン関数の実体を置くためのソースファイルを新たに追加する。

entity.c
#include "inline.h"
extern int add(int x, int y);

ポイントはexternのみをつけた関数プロトタイプにすることと、宣言箇所を1ヶ所だけにしておくこと。

$ gcc -std=c99 -O0 -o main_O0 entity.c a.c b.c main.c

こうすることによって、externの関数プロトタイプを宣言した箇所に関数の実体が作られる。

$ readelf -s main_O0 | grep add
    62: 00000000004004ed    20 FUNC    GLOBAL DEFAULT   15 add

関数が GLOBALになって、実体が1ヶ所だけになっていることが確認できたね。