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