このへんで書いた、どうなってるか自信が無いことがあったらとりあえず 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 とかするのがいい感じかなぁと思います。