C++のinlineはインライン展開するという意味ではない

C++の好きな指定子が inline で、inline は「インライン展開する」という意味ではないというのが好き。

「インライン展開する」という意味で inline を使うとしたら、

#include <stdio.h>
#include <stdlib.h>

inline int fibonacci(int x) {
  if (x <= 1) { return x; }

  return fibonacci(x-2) + fibonacci(x-1);
}

int main(int argc, char **argv) {
   printf("%d\n", fibonacci(atoi(argv[1])));
}

こういう再帰がいるような関数だとインライン展開できずにエラーになるはずである。 実際にはもちろんこれはエラーではない。

仕様を読んだことはないので厳密な定義は知らないが、inline 指定子は、「インライン展開的なことをやっても整合性とれるようにシンボルを定義してくれ」という指示になる。

インライン関数は通常ヘッダファイルに置かれるので、よくある使いかたは、

// h.h

inline int a(int x) { return x + 1; }
extern int b();
// a.cc
#include "h.h"
#include <stdio.h>

int main() {
  printf("%d\n", a(10) + b());
}
// b.cc
#include "h.h"

int b() { return a(10); }

こんな感じになるだろう。

ここで、inline 指定子がないとどうなるかというと、

// h.h
int a(int x) { return x + 1; } // inlnie をなくす
extern int b();
g++    -c -o a.o a.cc
g++    -c -o b.o b.cc
cc   a.o b.o   -o a
/usr/bin/ld: b.o: in function `a(int)':
b.cc:(.text+0x0): multiple definition of `a(int)'; a.o:a.cc:(.text+0x0): first defined here
collect2: エラー: ld はステータス 1 で終了しました
make: *** [<ビルトイン>: a] エラー 1

a.o と b.o の両方に関数aの実態ができてしまって multiple definition のエラーになる。

inline 指定子は、このmultiple definitionを起こさないでね、というのが仕様で、この仕様があることによって、インライン展開するような使いかたをしても問題が起こらない、という状況にできる。

じゃあこれ static とどう違うの?という話になるが、

// h.h
static int a(int x) { return x + 1; } // inline でなく static
extern int b();
g++    -c -o a.o a.cc
g++    -c -o b.o b.cc
cc   a.o b.o   -o a # OK!

staticは、ファイルローカルな関数を意味して、同じ名前でファイル毎に定義が違っても構わない。一方、inline は同じ名前で定義された変数は、全てのファイルで同じ定義になってないといけない、という違いがある。

#include <stdio.h>

YOUR_SPECIFIER int a(int x) { return x+1; }
extern int b();

int main() {
  printf("%d\n", a(10) + b());
}
// y.cc
YOUR_SPECIFIER int a(int x) { return x+1; }

int b() { return a(10); }

これは static でも inline でも動作は変わらない

$ g++ -DYOUR_SPECIFIER=static   -c -o x.o x.cc
$ g++ -DYOUR_SPECIFIER=static   -c -o y.o y.cc
$ cc   x.o y.o   -o x
$ ./x
$ 22

$ g++ -DYOUR_SPECIFIER=inline   -c -o x.o x.cc
$ g++ -DYOUR_SPECIFIER=inline   -c -o y.o y.cc
$ cc   x.o y.o   -o x
$ ./x
$ 22

ところが、このように a(int x) の定義を複数のファイルで違う定義にしてしまうと違いが出る。

// x.cc
#include <stdio.h>

YOUR_SPECIFIER int a(int x) { return x+100; }
extern int b();

int main() {
  printf("%d\n", a(10) + b());
}
// y.cc
YOUR_SPECIFIER int a(int x) { return x+1; }

int b() { return a(10); }
$ g++ -DYOUR_SPECIFIER=inline -O2 -fno-inline   -c -o x.o x.cc
$ g++ -DYOUR_SPECIFIER=inline -O2 -fno-inline   -c -o y.o y.cc
$ cc   x.o y.o   -o x
$ ./x
220
$ g++ -DYOUR_SPECIFIER=static -O2 -fno-inline   -c -o x.o x.cc
$ g++ -DYOUR_SPECIFIER=static -O2 -fno-inline   -c -o y.o y.cc
$ cc   x.o y.o   -o x
$./x
121

この場合、staticはきちんと定義されていて、どの環境でもこの通りに動くが、inlineを付けたほうは、値が何になるかはわからない。(仕様は見てないので未定義動作かどうかは知らない)

じゃあ inline より static のほうがミスが起こりづらくていいんじゃない?とずっと思ってたのだけど、去年くらいからいややっぱ static inline はインライン展開されなかった時に実体が意味もなく複数できてしまって.textサイズ無駄にでかくしてるよなぁ…と思うようになりなんも考えないでstatic inlineするようなことはやめました。

$ # static
$ ls -l x
-rwxr-xr-x 1 tanakmura tanakmura 15568  6月 23 00:54 x


$ # inline 
$ ls -l x
-rwxr-xr-x 1 tanakmura tanakmura 15528  6月 23 01:06 x

みんなも適切にinlineを使いましょう。

参考 : https://qiita.com/kktk-KO/items/075ce9a784d5d8296d53

(タイトルに反して staticinline の違いしか書いてない。static inlineinlinestaticの違いは…私もよく分からないのでみんなで調べよう!)

(inline 指定子の仕様にはインライン展開するって書いてあるか、全然書いてないかも知らないです。何も知らない。知ってること何もなかった)