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