息を吸うように objdump
このへんで書いた、どうなってるか自信が無いことがあったらとりあえず objdump しちゃえという話…とか言うと「アセンブリは…」みたいな雰囲気になることが多い気がするんですが、正直アセンブリなんか読めなくてもコードがどうなってるかくらいはわかるよん、という当たり前の話。
#include <stdio.h> struct S { explicit S(int x) : x_(x) {} int x() const { return x_; } int x_; }; int main() { S s(3); printf("%d\n", s.x()); }
とりあえずこんなコードをインライン化されるか知りたいとする。とりあえず -g つきでコンパイルして objdump 。
% g++ -g inline.cc -o inline % objdump -S -C inline | lv
コツはとりあえずコンパイルする時に -g つけとくのと objdump する時に -S にしておくことと出力はとりあえず less なり lv に喰わせること。 objdump -C は C++ のデマングル。個人的には gcc -S より objdump の方が見やすいかなぁと思う。
で、たぶんドバーと色々出てきてわけわかんね、と思うんだけど、内容は全く読まずに <main とかで検索する。するとなんか出てくる。
08048454 <main>: struct S { explicit S(int x) : x_(x) {} int x() const { return x_; } int x_; }; int main() { 8048454: 8d 4c 24 04 lea 0x4(%esp),%ecx 8048458: 83 e4 f0 and $0xfffffff0,%esp 804845b: ff 71 fc pushl -0x4(%ecx) 804845e: 55 push %ebp 804845f: 89 e5 mov %esp,%ebp 8048461: 51 push %ecx 8048462: 83 ec 24 sub $0x24,%esp S s(3); 8048465: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp) 804846c: 00 804846d: 8d 45 f8 lea -0x8(%ebp),%eax 8048470: 89 04 24 mov %eax,(%esp) 8048473: e8 2a 00 00 00 call 80484a2 <S::S(int)> printf("%d\n", s.x()); 8048478: 8d 45 f8 lea -0x8(%ebp),%eax 804847b: 89 04 24 mov %eax,(%esp) 804847e: e8 2d 00 00 00 call 80484b0 <S::x() const> 8048483: 89 44 24 04 mov %eax,0x4(%esp) 8048487: c7 04 24 7c 85 04 08 movl $0x804857c,(%esp) 804848e: e8 19 ff ff ff call 80483ac <printf@plt> 8048493: b8 00 00 00 00 mov $0x0,%eax } 8048498: 83 c4 24 add $0x24,%esp 804849b: 59 pop %ecx 804849c: 5d pop %ebp 804849d: 8d 61 fc lea -0x4(%ecx),%esp 80484a0: c3 ret
ちょっとひくけど、あまり深く考えることはなくて、要は printf の行で S::x() が call されてるかどうかが見たい。つまるところ call って機械語だけ知ってりゃ OK で、この場合 printf("%d\n", s.x()); って書いてある行の後で、
804847e: e8 2d 00 00 00 call 80484b0 <S::x() const>
って書いてあるからインライン化されてない。で次は g++ に -O をつけて同じように objdump する。
08048454 <main>: struct S { explicit S(int x) : x_(x) {} int x() const { return x_; } int x_; }; int main() { 8048454: 8d 4c 24 04 lea 0x4(%esp),%ecx 8048458: 83 e4 f0 and $0xfffffff0,%esp 804845b: ff 71 fc pushl -0x4(%ecx) 804845e: 55 push %ebp 804845f: 89 e5 mov %esp,%ebp 8048461: 51 push %ecx 8048462: 83 ec 14 sub $0x14,%esp S s(3); printf("%d\n", s.x()); 8048465: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp) 804846c: 00 804846d: c7 04 24 4c 85 04 08 movl $0x804854c,(%esp) 8048474: e8 33 ff ff ff call 80483ac <printf@plt> } 8048479: b8 00 00 00 00 mov $0x0,%eax 804847e: 83 c4 14 add $0x14,%esp 8048481: 59 pop %ecx 8048482: 5d pop %ebp 8048483: 8d 61 fc lea -0x4(%ecx),%esp 8048486: c3 ret
またどばーと書いてあるけど、 call 80484b0
次は定数がどうとか。
int main() { int sum = 0; int i; for (i = 0; i < 10; i++) { sum += i; } return sum; }
こんな 0 から 9 まで足し算するコード。結果は 45 == 0x2d になるはず。今度はとりあえず gcc -O -g とかでコンパイルして objdump してみると、
08048344 <main>: int main() { 8048344: 8d 4c 24 04 lea 0x4(%esp),%ecx 8048348: 83 e4 f0 and $0xfffffff0,%esp 804834b: ff 71 fc pushl -0x4(%ecx) 804834e: 55 push %ebp 804834f: 89 e5 mov %esp,%ebp 8048351: 51 push %ecx int i; for (i = 0; i < 10; i++) { sum += i; } return sum; } 8048352: b8 2d 00 00 00 mov $0x2d,%eax 8048357: 59 pop %ecx 8048358: 5d pop %ebp 8048359: 8d 61 fc lea -0x4(%ecx),%esp 804835c: c3 ret
またよくわからんけど、なんとなく一見してループとか無さそう。なんか短い。ループとかあると jmp とか j で始まる命令があるはず (x86 なら。他だと b (branch) とかが多いのかな) 。そんなことより決定的なのは
8048352: b8 2d 00 00 00 mov $0x2d,%eax
という行で、 $0x2d ってのはどう考えても計算結果が既に入ってる感じ。最適化されて return 45; みたいなコードになってることが想像できる。
一方 -O 消してみると、
08048344 <main>: int main() { 8048344: 8d 4c 24 04 lea 0x4(%esp),%ecx 8048348: 83 e4 f0 and $0xfffffff0,%esp 804834b: ff 71 fc pushl -0x4(%ecx) 804834e: 55 push %ebp 804834f: 89 e5 mov %esp,%ebp 8048351: 51 push %ecx 8048352: 83 ec 10 sub $0x10,%esp int sum = 0; 8048355: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%ebp) int i; for (i = 0; i < 10; i++) { 804835c: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%ebp) 8048363: eb 0a jmp 804836f <main+0x2b> sum += i; 8048365: 8b 45 f8 mov -0x8(%ebp),%eax 8048368: 01 45 f4 add %eax,-0xc(%ebp) int main() { int sum = 0; int i; for (i = 0; i < 10; i++) { 804836b: 83 45 f8 01 addl $0x1,-0x8(%ebp) 804836f: 83 7d f8 09 cmpl $0x9,-0x8(%ebp) 8048373: 7e f0 jle 8048365 <main+0x21> sum += i; } return sum; 8048375: 8b 45 f4 mov -0xc(%ebp),%eax } 8048378: 83 c4 10 add $0x10,%esp 804837b: 59 pop %ecx 804837c: 5d pop %ebp 804837d: 8d 61 fc lea -0x4(%ecx),%esp 8048380: c3 ret
なんかすんごく長い。どこにも 0x2d という文字列が見当たらない。いかにもループしてそう。よって最適化されてない。そんだけ。
というわけでベンチマークとかするより機械語見る(というより眺める)方がはるかに速いことが多いよなぁというような話。 objdump はコワクナイヨ。
あとはまぁ、機械語生成するようなコード書いてる場合は、とりあえず生成したコードをファイルに書き出して、 objdump -b binary -m i386 -D とかするのがいい感じかなぁと思います。