コミケ告知

サークル活動の詳細は circle タグの記事へ。
2013年1月1日火曜日

ソースファイルに書いたメンバ関数ってインライン展開されるんだっけ?

# はてなダイアリーから移動した記事です。あまり真面目に整形していません。

ヘッダファイルでクラス宣言中に書けばインライン化されやすいのは知ってるけれども、ソースファイルに書くとどうなるんだっけ?と、突然気になって調べてみました。もちろん、そのクラス内の別のメンバ関数から呼び出す場合の話です。

// Hoge.h
#include <stdio.h>

class Hoge {
 public:
  void moge();
  void pub(int &x);
  void pub_i1(int &x) {
    ++x;
    printf("%d ", x);
  }
  void pub_i2(int &x);

 private:
  void priv(int &x);
  void priv_i1(int &x) {
    ++x;
    printf("%d ", x);
  }
  void priv_i2(int &x);
};

// Hoge.cpp
#include "Hoge.h"

void Hoge::moge() {
  int n = 0;
  pub(n);
  pub_i1(n);
  pub_i2(n);
  priv(n);
  priv_i1(n);
  priv_i2(n);
  printf("%d\n", n);
}
void Hoge::pub(int &x) {
    ++x;
    printf("%d ", x);
}
inline void Hoge::pub_i2(int &x) {
    ++x;
    printf("%d ", x);
}
void Hoge::priv(int &x) {
    ++x;
    printf("%d ", x);
}
inline void Hoge::priv_i2(int &x) {
    ++x;
    printf("%d ", x);
}

Hoge::moge()で何やってるのか確認します。

gcc

  • version 4.5.3 (GCC)

-Sオプションで出力。

デフォルトの最適化オプション
    movl    $0, -12(%ebp)
    leal    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    __ZN4Hoge3pubERi
    leal    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    __ZN4Hoge6pub_i1ERi
    leal    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    __ZN4Hoge6pub_i2ERi
    leal    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    __ZN4Hoge4privERi
    leal    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    __ZN4Hoge7priv_i1ERi
    leal    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    __ZN4Hoge7priv_i2ERi
    movl    -12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    $LC1, (%esp)
    call    _printf
    leave

全部call。

-O2
    movl    8(%ebp), %ebx
    leal    -12(%ebp), %esi
    movl    %esi, 4(%esp)
    movl    $0, -12(%ebp)
    movl    %ebx, (%esp)
    call    __ZN4Hoge3pubERi
    movl    -12(%ebp), %eax
    movl    $LC0, (%esp)
    addl    $1, %eax
    movl    %eax, -12(%ebp)
    movl    %eax, 4(%esp)
    call    _printf
    movl    -12(%ebp), %eax
    movl    $LC0, (%esp)
    addl    $1, %eax
    movl    %eax, -12(%ebp)
    movl    %eax, 4(%esp)
    call    __printf
    movl    %esi, 4(%esp)
    movl    %ebx, (%esp)
    call    __ZN4Hoge4privERi
    movl    -12(%ebp), %eax
    movl    $LC0, (%esp)
    addl    $1, %eax
    movl    %eax, -12(%ebp)
    movl    %eax, 4(%esp)
    call    __printf
    movl    -12(%ebp), %eax
    movl    $LC0, (%esp)
    addl    $1, %eax
    movl    %eax, -12(%ebp)
    movl    %eax, 4(%esp)
    call    __printf
    movl    -12(%ebp), %eax
    movl    $LC1, (%esp)
    movl    %eax, 4(%esp)
    call    _printf
    addl    $32, %esp
    popl    %ebx

ヘッダファイル内のクラス定義で記述された関数と、ソースファイル中でinline指定された関数が展開されました。
publicでinlineな関数も展開されています。これで外部から呼ぶと…たとえばmain関数からpub_i2()を呼ぶと、リンクエラーになります。inlineキーワードはあくまでもヒントです!というスタンスかと思いきや、インライン化を優先するんですね。意外でした。

#include "Hoge.h"

int main(int argc, char **argv) {
  Hoge hoge;
  int n = 0;
  hoge.pub(n);
  hoge.pub_i1(n);
  hoge.pub_i2(n);

  return 0;
}
% gcc Main.cpp Hoge.o
/cygdrive/z/Temp/ccgFs4xP.o:Main.cpp:(.text+0x4e): undefined reference to `Hoge::pub_i2(int&)'
collect2: ld returned 1 exit status

cl.exe (Visual Studio)

  • Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86

/FAオプションで出力。

デフォルトの最適化オプション
    push    ebp
    mov ebp, esp
    sub esp, 8
    mov DWORD PTR _this$[ebp], ecx
; Line 4
    mov DWORD PTR _n$[ebp], 0
; Line 5
    lea eax, DWORD PTR _n$[ebp]
    push    eax
    mov ecx, DWORD PTR _this$[ebp]
    call    ?pub@Hoge@@QAEXAAH@Z            ; Hoge::pub
; Line 6
    lea ecx, DWORD PTR _n$[ebp]
    push    ecx
    mov ecx, DWORD PTR _this$[ebp]
    call    ?pub_i1@Hoge@@QAEXAAH@Z         ; Hoge::pub_i1
; Line 7
    lea edx, DWORD PTR _n$[ebp]
    push    edx
    mov ecx, DWORD PTR _this$[ebp]
    call    ?pub_i2@Hoge@@QAEXAAH@Z         ; Hoge::pub_i2
; Line 8
    lea eax, DWORD PTR _n$[ebp]
    push    eax
    mov ecx, DWORD PTR _this$[ebp]
    call    ?priv@Hoge@@AAEXAAH@Z           ; Hoge::priv
; Line 9
    lea ecx, DWORD PTR _n$[ebp]
    push    ecx
    mov ecx, DWORD PTR _this$[ebp]
    call    ?priv_i1@Hoge@@AAEXAAH@Z        ; Hoge::priv_i1
; Line 10
    lea edx, DWORD PTR _n$[ebp]
    push    edx
    mov ecx, DWORD PTR _this$[ebp]
    call    ?priv_i2@Hoge@@AAEXAAH@Z        ; Hoge::priv_i2
; Line 11
    mov eax, DWORD PTR _n$[ebp]
    push    eax
    push    OFFSET $SG3877
    call    _printf
    add esp, 8
; Line 12
    mov esp, ebp
    pop ebp
    ret 0

全部call。

-O2
; Line 5
    push    1
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 6
    push    2
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 7
    push    3
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 8
    push    4
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 9
    push    5
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 10
    push    6
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 11
    push    6
    push    OFFSET ??_C@_03PMGGPEJJ@?$CFd?6?$AA@
    call    _printf
    add esp, 56                 ; 00000038H
; Line 12
    ret 0

全部展開。
さすがに関数の中身がcallの手続きより語数少ないのは少なすぎたか、と関数内のprintfを増量してみましたが、その程度では結果は変わりませんでした。

-Ox
; Line 5
    push    1
    push    OFFSET $SG3882
    call    _printf
; Line 6
    push    2
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 7
    push    3
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 8
    push    4
    push    OFFSET $SG3891
    call    _printf
; Line 9
    push    5
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 10
    push    6
    push    OFFSET ??_C@_03JDANDILB@?$CFd?5?$AA@
    call    _printf
; Line 11
    push    6
    push    OFFSET $SG3877
    call    _printf
    add esp, 56                 ; 00000038H
; Line 12
    ret 0

全部展開。
なお、-O2も-Oxも、cl Main.cpp Hoge.obj としてみたところ、何事もなくリンク・実行とも成功しました。objdump -rするとゴチャゴチャついてます。インライン化するけど実態も定義しておく、と。速度を最適化してるんだから、サイズでかくなることに文句いっちゃいかんだろ、ということですかね。

総括

gccは想像通りの動作をする。
cl.exeは予想以上にばりばり展開する。

gccはとても小さなプラットフォームまで対象なので、仕様通りに動いてくれないと困る…という感じでしょうか。
とにかく、private関数の定義にinlineと書けばinline化されそうな事はわかりました。

0 件のコメント:

コメントを投稿