インライン展開についての追加調査

昨日の記事で、インライン展開について調べましたが、
よくよく考えると片手落ちだったので追加調査しました。

インライン展開がどう展開されるのかを調べた

調査内容

昨日はヘッダと実装が書いてあるファイルとでの差は調べましたが、
同じファイル内でどのようになるかは調べていませんでしたので、
追加調査しました。

方法は昨日と同じく、-Sオプションをつけて結果を見ます。

ソースコード

以下のようなファイルを使います

test.cpp

#include <stdio.h>
#include "func.h"

int main() {
  TestA test;
  int a = test.getDirect();
  int b = test.getThrough();
  printf("%d %d\n", a, b);
  return 0;
}

func.h

class TestA{
public:
  int getDirect();
  int getThrough();
private:
  int getPrivate();
};

func.cpp

#include "func.h"

int TestA::getDirect(){
  return 42;
}

int TestA::getThrough(){
  return getDirect() + getPrivate();
}

int TestA::getPrivate(){
  return 73;
}

func.cppのgetThrough関数に対して、インライン展開が行われる事が予想されますが、
getDirect関数は外部からも呼ばれるため、展開しても関数そのものは必要です。
getPrivate関数は外部から呼ばれないため、インライン展開後に消してしまう可能性があります。

最適化しない場合

3つの関数は全て普通にコンパイルされました。

最適化した場合

同じファイル内のインライン展開

getThrough関数は以下のようになりました。

__ZN5TestA10getThroughEv:               ## @_ZN5TestA10getThroughEv
pushq	%rbp
movq	%rsp, %rbp
movl	$115, %eax
popq	%rbp
retq

42+73=115なので、インライン展開と最適化がされているのがわかります。
そのため、同じファイル内なら、ヘッダに書いていなくても勝手にインライン展開されるみたいです。

よくよく考えると、includeが処理されるのはプリプロセスのタイミングで処理されます。
そのため、コンパイラは全てのヘッダファイルが展開された状態のテキストしか受け取りません。
なので、どのファイルに関数が書かれているかは関係ないと思われます。

インライン展開後の関数

getDirect関数とgetPrivate関数ですが、どちらも最適化していない場合と同じく、
関数が出力されていました。

getDirect関数はpublic指定されているため、
外部から参照される場合のために展開後も関数が残されるのは予想通りです。

getPrivate関数はprivate指定されているため継承先のクラスから呼ばれることはなく、
かつこのクラスはfriend指定もされていないため、外部から呼び出されることはありません。
そのため、インライン展開後をして同じクラス内から呼ばれないことが確定した時点で、
この関数を出力する必要は無さそうなのですが、なぜ消さないのかは不明です。

まとめ

  • 同じファイル内でもインライン展開は行われる
    • O3ならinline宣言とか要らない
  • インライン展開しても、展開元の関数は残る
    • ヘッダファイルに書いた関数は展開元が消えたので挙動が違う
    • publicはリンクされるまで呼ばれるかわからないので消せない
      • privateは呼ばれない事が確定するはずだが何故か消さない