インライン展開されない場合の挙動
以前、[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ヶ所だけになっていることが確認できたね。
0 件のコメント:
コメントを投稿