用匯編對C++代碼的優(yōu)化前后進行分析
下面通過對C++程序匯編代碼的分析,加深編譯器對C++程序優(yōu)化的理解1. C++程序如下:
#include<cstdio>
using std::printf;
class MyParent
{
public:
MyParent()
{}
~MyParent()
{}
void DoJob()
{
Test();
}
private:
virtual void Test()
{
printf("Parent::Test\n");
}
};
class MyChild:public MyParent
{
public:
MyChild()
{}
~MyChild()
{}
private:
virtual void Test()
{
printf("Child::Test\n");
}
};
int main()
{
MyChild obj;
MyParent *p=&obj;
p->DoJob();
}
2.要注意的概念和寄存器表示的約定
2.1 SFP(Stack Frame Pointer)
在Intel的CPU中ESP永遠指向棧頂,EBP永遠指向棧底,同時棧由高地址向低地址增長。
通常在調(diào)用一個函數(shù)的時候,下一條指令地址被入棧,方便在函數(shù)結(jié)束時通過ret返回到下一條
指令繼續(xù)執(zhí)行。在查看調(diào)用函數(shù)的代碼時,經(jīng)常會看到下面的指令
pushl %ebp
mov %esp, %ebp //建立一個SFP
... ...
leave //相當于mov %ebp, %esp和popl %ebp兩條指令
ret
這個SFP用來確定當前函數(shù)棧的邊界,而且可以通過EBP得到上一層函數(shù)棧的棧底。一般通過這種
方式來取傳入的參數(shù)
mov 0x8(%ebp), xxx
因為建立SFP時pushl的EBP占用4個字節(jié),而之前壓入棧的EIP又占用4個字節(jié)。
2.2 C++中的vtable和vptr
C++的每個對象都會有一個vptr指針,用來指向vtable的地址,而vtable是一個函數(shù)指
針數(shù)組,分別指向各個類定義的虛函數(shù)。在GCC編譯的代碼中,vptr在對象的低地址。
*vptr -->得到vtable
(*vptr)[1] -->得到vtable[1]
2.3 顏色約定
這種顏色表示ESP,這種顏色表示EBP,這種顏色表示應(yīng)該要注意的數(shù)據(jù)。
3.未進行-O2 優(yōu)化時的匯編代碼
代碼如下:
Dump of assembler code for function main:
0x080485a0 <main+0>: lea 0x4(%esp),%ecx
0x080485a4 <main+4>: and $0xfffffff0,%esp
0x080485a7 <main+7>: pushl 0xfffffffc(%ecx)
0x080485aa <main+10>: push %ebp
0x080485ab <main+11>: mov %esp,%ebp
0x080485ad <main+13>: push %ebx
0x080485ae <main+14>: push %ecx
0x080485af <main+15>: sub $0x30,%esp
0x080485b2 <main+18>: lea 0xfffffff0(%ebp),%eax
0x080485b5 <main+21>: mov %eax,(%esp)
0x080485b8 <main+24>: call 0x8048650 <_ZN7MyChildC1Ev>
0x080485bd <main+29>: lea 0xfffffff0(%ebp),%eax
0x080485c0 <main+32>: mov %eax,0xfffffff4(%ebp)
0x080485c3 <main+35>: mov 0xfffffff4(%ebp),%eax
0x080485c6 <main+38>: mov %eax,(%esp)
0x080485c9 <main+41>: call 0x8048630 <_ZN8MyParent5DoJobEv>
0x080485ce <main+46>: lea 0xfffffff0(%ebp),%eax
0x080485d1 <main+49>: mov %eax,(%esp)
0x080485d4 <main+52>: call 0x8048670 <_ZN7MyChildD1Ev>
0x080485d9 <main+57>: mov $0x0,%eax
0x080485de <main+62>: mov %eax,0xffffffe4(%ebp)
0x080485e1 <main+65>: jmp 0x8048602 <main+98>
0x080485e3 <main+67>: mov %eax,0xffffffe0(%ebp)
0x080485e6 <main+70>: mov 0xffffffe0(%ebp),%ebx
0x080485e9 <main+73>: lea 0xfffffff0(%ebp),%eax
0x080485ec <main+76>: mov %eax,(%esp)
0x080485ef <main+79>: call 0x8048670 <_ZN7MyChildD1Ev>
0x080485f4 <main+84>: mov %ebx,0xffffffe0(%ebp)
0x080485f7 <main+87>: mov 0xffffffe0(%ebp),%eax
0x080485fa <main+90>: mov %eax,(%esp)
0x080485fd <main+93>: call 0x8048480 <_init+100>
0x08048602 <main+98>: mov 0xffffffe4(%ebp),%eax
0x08048605 <main+101>: add $0x30,%esp
0x08048608 <main+104>: pop %ecx
0x08048609 <main+105>: pop %ebx
0x0804860a <main+106>: pop %ebp
0x0804860b <main+107>: lea 0xfffffffc(%ecx),%esp
0x0804860e <main+110>: ret
0x0804860f <main+111>: nop
End of assembler dump.
從這里開始分析整個代碼:
Dump of assembler code for function main:
0x080485a0 <main+0>: lea 0x4(%esp),%ecx
0x080485a4 <main+4>: and $0xfffffff0,%esp
0x080485a7 <main+7>: pushl 0xfffffffc(%ecx)
0x080485aa <main+10>: push %ebp
0x080485ab <main+11>: mov %esp,%ebp
--------------------------------------------------------------------------
上面這段代碼做了進入main函數(shù)時的準備工作,同時創(chuàng)建了SPF,此時的堆棧與寄存器如下:
0xbfbfe830: 0xbfbfe9a0 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0xbfbfe868 0x2824a6b9
0xbfbfe850: 0x00000018 0x00000001 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe894
5: /x $ebx = 0x1
4: /x $ecx = 0xbfbfe870
3: /x $edx = 0x0
2: /x $ebp = 0xbfbfe858
1: /x $esp = 0xbfbfe858
========================== 下面繼續(xù)代碼======================================
0x080485ad <main+13>: push %ebx
0x080485ae <main+14>: push %ecx
0x080485af <main+15>: sub $0x30,%esp
0x080485b2 <main+18>: lea 0xfffffff0(%ebp),%eax
0x080485b5 <main+21>: mov %eax,(%esp)
0x080485b8 <main+24>: call 0x8048650 <_ZN7MyChildC1Ev>
--------------------------------------------------------------------------
這兩段代碼分別完成:
1.首先保存EBX和ECX的數(shù)據(jù),然后準備了48(0x30)個字節(jié)的空間,同時保存MyChild的this指
針的地址到EAX。
2.把this指針入棧,調(diào)用MyChild::MyChild()進行構(gòu)造。
調(diào)用MyChild::MyChild()之前的堆棧與寄存器內(nèi)容如下:
0xbfbfe800: 0x28070814 0xbfbfe844 0x2804d998 0x28074e24
0xbfbfe810: 0x00000001 0xbfbfe834 0x00000001 0x00000000
0xbfbfe820: 0xbfbfe848[1] 0x00000001 0xbfbfe980 0xbfbfe988
0xbfbfe830: 0xbfbfe9a0 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0xbfbfe868[2] 0x2824a6b9[3]
0xbfbfe850: 0xbfbfe870 0x00000001 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe848
5: /x $ebx = 0x1
4: /x $ecx = 0xbfbfe870
3: /x $edx = 0x0
2: /x $ebp = 0xbfbfe858
1: /x $esp = 0xbfbfe820
這里[1]保存著this指針(也就是[2]的地址),注意[2]后面的[3],就是MyParent的指針空間
(MyParent *p),現(xiàn)在保存的地址為0x2824a6b9還沒有進行賦值。
在C++中一個空類的sizeof大小為1,這個值是在編譯時期就計算出來的,而在實際情況中,如果
一個空類沒有虛函數(shù),那么在內(nèi)存中占1個字節(jié),否則就占用4個字節(jié)(vptr用)。
此時[2]這塊空間就是vptr指針的空間,[2]的地址則是this指針所指向的地址,這個時候vptr還
沒有初始化。
下面要開始調(diào)用MyChild::MyChild(),代碼如下:
Dump of assembler code for function _ZN7MyChildC1Ev:
0x08048650 <_ZN7MyChildC1Ev+0>: push %ebp
0x08048651 <_ZN7MyChildC1Ev+1>: mov %esp,%ebp
0x08048653 <_ZN7MyChildC1Ev+3>: sub $0x8,%esp
0x08048656 <_ZN7MyChildC1Ev+6>: mov 0x8(%ebp),%eax
0x08048659 <_ZN7MyChildC1Ev+9>: mov %eax,(%esp)
0x0804865c <_ZN7MyChildC1Ev+12>: call 0x8048610 <_ZN8MyParentC2Ev>
0x08048661 <_ZN7MyChildC1Ev+17>: mov $0x8048780,%edx
0x08048666 <_ZN7MyChildC1Ev+22>: mov 0x8(%ebp),%eax
0x08048669 <_ZN7MyChildC1Ev+25>: mov %edx,(%eax)
0x0804866b <_ZN7MyChildC1Ev+27>: leave
0x0804866c <_ZN7MyChildC1Ev+28>: ret
0x0804866d <_ZN7MyChildC1Ev+29>: nop
0x0804866e <_ZN7MyChildC1Ev+30>: nop
0x0804866f <_ZN7MyChildC1Ev+31>: nop
End of assembler dump.
我們從這里開始分析MyChild::MyChild()的代碼:
Dump of assembler code for function _ZN7MyChildC1Ev:
0x08048650 <_ZN7MyChildC1Ev+0>: push %ebp
0x08048651 <_ZN7MyChildC1Ev+1>: mov %esp,%ebp
0x08048653 <_ZN7MyChildC1Ev+3>: sub $0x8,%esp
0x08048656 <_ZN7MyChildC1Ev+6>: mov 0x8(%ebp),%eax
--------------------------------------------------------------------------
這里建立SFP后,最后把剛才傳入的this指針保存到EAX中。從堆棧的內(nèi)容可以知道,EBP+8后剛好
是[2],地址為 0xbfbfe820,這里保存就是入?yún)ⅲ籟1]的位置保存著EIP。
0xbfbfe800: 0x28070814 0xbfbfe844 0x2804d998 0x28074e24
0xbfbfe810: 0x00000001 0xbfbfe834 0xbfbfe858 0x080485bd[1]
0xbfbfe820: 0xbfbfe848[2] 0x00000001 0xbfbfe980 0xbfbfe988
0xbfbfe830: 0xbfbfe9a0 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0xbfbfe868 0x2824a6b9
0xbfbfe850: 0xbfbfe870 0x00000001 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe848
5: /x $ebx = 0x1
4: /x $ecx = 0xbfbfe870
3: /x $edx = 0x0
2: /x $ebp = 0xbfbfe818
1: /x $esp = 0xbfbfe810
========================== 下面繼續(xù)代碼======================================
0x08048659 <_ZN7MyChildC1Ev+9>: mov %eax,(%esp)
0x0804865c <_ZN7MyChildC1Ev+12>: call 0x8048610 <_ZN8MyParentC2Ev>
--------------------------------------------------------------------------
再次把this指針入棧,然后調(diào)用MyChild的基類MyParent::MyParent構(gòu)造函數(shù),代碼如下:
Dump of assembler code for function _ZN8MyParentC2Ev:
0x08048610 <_ZN8MyParentC2Ev+0>: push %ebp
0x08048611 <_ZN8MyParentC2Ev+1>: mov %esp,%ebp
0x08048613 <_ZN8MyParentC2Ev+3>: mov $0x80487b8,%edx
0x08048618 <_ZN8MyParentC2Ev+8>: mov 0x8(%ebp),%eax
0x0804861b <_ZN8MyParentC2Ev+11>: mov %edx,(%eax)
0x0804861d <_ZN8MyParentC2Ev+13>: pop %ebp
0x0804861e <_ZN8MyParentC2Ev+14>: ret
0x0804861f <_ZN8MyParentC2Ev+15>: nop
End of assembler dump.
我們從這里開始分析MyParent::MyParent的代碼:
0x08048610 <_ZN8MyParentC2Ev+0>: push %ebp
0x08048611 <_ZN8MyParentC2Ev+1>: mov %esp,%ebp
0x08048613 <_ZN8MyParentC2Ev+3>: mov $0x80487b8,%edx
--------------------------------------------------------------------------
同樣地建立SFP.但是0x80487b8是什么?這個是MyParent的vtable地址,而EDX我們就是所謂
的vptr指針了。我們可以驗證一下:
(gdb) x 0x80487b8
0x80487b8 <_ZTV8MyParent+8>: 0x080486b0
由于vtable是一個函數(shù)指針數(shù)組,所以我們用"x 0x80487b8"得到的0x080486b0其實是vtable[0],
接下來:
(gdb) x 0x080486b0
0x80486b0 <_ZN8MyParent4TestEv>: 0x83e58955
這個才是MyParent::Test的真實地址。用C++filt還原上面的"_ZTV8MyParent+8"和
"_ZN8MyParent4TestEv":
_ZTV8MyParent+8 vtable for MyParent+8
_ZN8MyParent4TestEv MyParent::Test()
==========================下面 繼續(xù)代碼======================================
0x08048618 <_ZN8MyParentC2Ev+8>: mov 0x8(%ebp),%eax
0x0804861b <_ZN8MyParentC2Ev+11>: mov %edx,(%eax)
--------------------------------------------------------------------------
得到MyParent的vtable地址后,我們需要設(shè)置MyChild的vptr指向MyParent的vtable。這里
首先取出來的就是MyChild對象的this指針,前面也說過了,vptr指針在對象的頭4個字節(jié),所以這時候
EAX保存的也就是MyChild的vptr的地址。
然后把MyParent的vtable地址賦值給MyChild的vptr,堆棧和寄存器內(nèi)容如下:
0xbfbfe800: 0x28070814 0xbfbfe844 0xbfbfe818 0x08048661
0xbfbfe810: 0xbfbfe848[1] 0xbfbfe834 0xbfbfe858 0x080485bd
0xbfbfe820: 0xbfbfe848 0x00000001 0xbfbfe980 0xbfbfe988
0xbfbfe830: 0xbfbfe9a0 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0x080487b8[2] 0x2824a6b9
0xbfbfe850: 0xbfbfe870 0x00000001 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe848
5: /x $ebx = 0x1
4: /x $ecx = 0xbfbfe870
3: /x $edx = 0x80487b8
2: /x $ebp = 0xbfbfe808
1: /x $esp = 0xbfbfe808
由于這里ESP和EBP是一樣的地址,[1]實際上是0x8(%ebp)的位置,[2]是MyChild對象的vptr指針
的空間,現(xiàn)在已經(jīng)指向MyParent的vtable地址了
==========================下面 繼續(xù)代碼======================================
0x0804861d <_ZN8MyParentC2Ev+13>: pop %ebp
0x0804861e <_ZN8MyParentC2Ev+14>: ret
0x0804861f <_ZN8MyParentC2Ev+15>: nop
--------------------------------------------------------------------------
由于沒有增加堆棧空間,EBP和ESP是一樣的,所以不需要LEAVE指令,直接POP出EBP。
從這里結(jié)束對MyParent::MyParent()的分析,下面繼續(xù)MyChild::MyChild()的分析
==========================下面 繼續(xù)代碼======================================
0x08048661 <_ZN7MyChildC1Ev+17>: mov $0x8048780,%edx
0x08048666 <_ZN7MyChildC1Ev+22>: mov 0x8(%ebp),%eax
0x08048669 <_ZN7MyChildC1Ev+25>: mov %edx,(%eax)
0x0804866b <_ZN7MyChildC1Ev+27>: leave
0x0804866c <_ZN7MyChildC1Ev+28>: ret
0x0804866d <_ZN7MyChildC1Ev+29>: nop
0x0804866e <_ZN7MyChildC1Ev+30>: nop
0x0804866f <_ZN7MyChildC1Ev+31>: nop
--------------------------------------------------------------------------
從MyParent::MyParent()返回以后,現(xiàn)在執(zhí)行MyChild::MyChild()。這里首先也是把vtable
地址保存到EDX,取出MyChild對象的this指針,然后設(shè)置vptr指向MyChild的vtable.由于整個構(gòu)造函數(shù)
都是空的,所以這里設(shè)置完vptr后就直接返回,下面是設(shè)置完vptr后的寄存器和堆棧內(nèi)容:
0xbfbfe800: 0x28070814 0xbfbfe844 0xbfbfe818 0x08048661
0xbfbfe810: 0xbfbfe848 0xbfbfe834 0xbfbfe858 0x080485bd
0xbfbfe820: 0xbfbfe848[1] 0x00000001 0xbfbfe980 0xbfbfe988
0xbfbfe830: 0xbfbfe9a0 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0x08048780[2] 0x2824a6b9
0xbfbfe850: 0xbfbfe870 0x00000001 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe848
5: /x $ebx = 0x1
4: /x $ecx = 0xbfbfe870
3: /x $edx = 0x8048780
2: /x $ebp = 0xbfbfe818
1: /x $esp = 0xbfbfe810
這里[1]是0x8(%ebp)的位置,[2]是(%eax)的位置。可以看到已經(jīng)把EDX的值賦給[2]了。
這里結(jié)束對MyChild::MyChild()代碼的分析。
==========================下面繼 續(xù)代碼======================================
0x080485bd <main+29>: lea 0xfffffff0(%ebp),%eax
0x080485c0 <main+32>: mov %eax,0xfffffff4(%ebp)
0x080485c3 <main+35>: mov 0xfffffff4(%ebp),%eax
0x080485c6 <main+38>: mov %eax,(%esp)
0x080485c9 <main+41>: call 0x8048630 <_ZN8MyParent5DoJobEv>
--------------------------------------------------------------------------
構(gòu)造函數(shù)執(zhí)行完畢后,我們把MyChild對象的指針給了MyParent指針,然后調(diào)用了DoJob方法。
在調(diào)用DoJob之前的寄存器和堆棧地址為:
0xbfbfe800: 0x28070814 0xbfbfe844 0xbfbfe818 0x08048661
0xbfbfe810: 0xbfbfe848 0xbfbfe834 0xbfbfe858 0x080485bd
0xbfbfe820: 0xbfbfe848[3] 0x00000001 0xbfbfe980 0xbfbfe988
0xbfbfe830: 0xbfbfe9a0 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0x08048780[1] 0xbfbfe848[2]
0xbfbfe850: 0xbfbfe870 0x00000001 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe848
5: /x $ebx = 0x1
4: /x $ecx = 0xbfbfe870
3: /x $edx = 0x8048780
2: /x $ebp = 0xbfbfe858
1: /x $esp = 0xbfbfe820
首先保存[1]的地址(this指針)到EAX,再保存到[2],這里是"MyParent *p"的指針空間。這樣子就完
成了語句"MyParent *p=&obj"的賦值,然后再把"MyParent *p"所保存的地址入棧為調(diào)用MyParent::
DoJob做準備。
MyParent::DoJob的代碼如下:
Dump of assembler code for function _ZN8MyParent5DoJobEv:
0x08048630 <_ZN8MyParent5DoJobEv+0>: push %ebp
0x08048631 <_ZN8MyParent5DoJobEv+1>: mov %esp,%ebp
0x08048633 <_ZN8MyParent5DoJobEv+3>: sub $0x8,%esp
0x08048636 <_ZN8MyParent5DoJobEv+6>: mov 0x8(%ebp),%eax
0x08048639 <_ZN8MyParent5DoJobEv+9>: mov (%eax),%eax
0x0804863b <_ZN8MyParent5DoJobEv+11>: mov (%eax),%edx
0x0804863d <_ZN8MyParent5DoJobEv+13>: mov 0x8(%ebp),%eax
0x08048640 <_ZN8MyParent5DoJobEv+16>: mov %eax,(%esp)
0x08048643 <_ZN8MyParent5DoJobEv+19>: call *%edx
0x08048645 <_ZN8MyParent5DoJobEv+21>: leave
0x08048646 <_ZN8MyParent5DoJobEv+22>: ret
0x08048647 <_ZN8MyParent5DoJobEv+23>: nop
0x08048648 <_ZN8MyParent5DoJobEv+24>: nop
0x08048649 <_ZN8MyParent5DoJobEv+25>: nop
0x0804864a <_ZN8MyParent5DoJobEv+26>: nop
0x0804864b <_ZN8MyParent5DoJobEv+27>: nop
0x0804864c <_ZN8MyParent5DoJobEv+28>: nop
0x0804864d <_ZN8MyParent5DoJobEv+29>: nop
0x0804864e <_ZN8MyParent5DoJobEv+30>: nop
0x0804864f <_ZN8MyParent5DoJobEv+31>: nop
End of assembler dump.
從這里開始MyParent::DoJob的代碼分析:
0x08048630 <_ZN8MyParent5DoJobEv+0>: push %ebp
0x08048631 <_ZN8MyParent5DoJobEv+1>: mov %esp,%ebp
0x08048633 <_ZN8MyParent5DoJobEv+3>: sub $0x8,%esp
0x08048636 <_ZN8MyParent5DoJobEv+6>: mov 0x8(%ebp),%eax
0x08048639 <_ZN8MyParent5DoJobEv+9>: mov (%eax),%eax
0x0804863b <_ZN8MyParent5DoJobEv+11>: mov (%eax),%edx
0x0804863d <_ZN8MyParent5DoJobEv+13>: mov 0x8(%ebp),%eax
0x08048640 <_ZN8MyParent5DoJobEv+16>: mov %eax,(%esp)
0x08048643 <_ZN8MyParent5DoJobEv+19>: call *%edx
--------------------------------------------------------------------------
上面的代碼分成三部分:
1.這個時候已經(jīng)把堆棧中的 this指針取出來保存到EAX中。
2.取出MyChild的vtable地址保存到EAX,再取出vtable[0]保存到EDX
3.取出堆棧中的this指針保存到EAX,同時入棧,然后調(diào)用vtable[0]的函數(shù)
在調(diào)用vtable[0]函數(shù)之前的寄存器和堆棧數(shù)據(jù)如下:
0xbfbfe800: 0x28070814 0xbfbfe844 0xbfbfe818 0x08048661
0xbfbfe810: 0xbfbfe848 0xbfbfe834 0xbfbfe858 0x080485ce
0xbfbfe820: 0xbfbfe848[1] 0x00000001 0xbfbfe980 0xbfbfe988
0xbfbfe830: 0xbfbfe9a0 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0x08048780[2] 0xbfbfe848
0xbfbfe850: 0xbfbfe870 0x00000001 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe848
5: /x $ebx = 0x1
4: /x $ecx = 0xbfbfe870
3: /x $edx = 0x8048690
2: /x $ebp = 0xbfbfe818
1: /x $esp = 0xbfbfe810
先從[1]的地方取出this指針保存到EAX中,然后再通過"mov (%eax),%eax",取出[2](0x08048780)
保存到EAX,最后再通過"mov (%eax),%edx"取出vtable保存到EDX中,我們驗證一下:
(gdb) x 0x08048780
0x8048780 <_ZTV7MyChild+8>: 0x08048690
此時EDX保存的就是0x08048690,最后 我們調(diào)用"call *%edx"的時候,就等于調(diào)用vtable[0]:
(gdb) x 0x08048690
0x8048690 <_ZN7MyChild4TestEv>: 0x83e58955
用c++filt對上面的字符串進行還原:
_ZTV7MyChild+8 vtable for MyChild+8
_ZN7MyChild4TestEv MyChild::Test()
現(xiàn)在下一步就直接進入MyChild::Test執(zhí)行,代碼如下:
Dump of assembler code for function _ZN7MyChild4TestEv:
0x08048690 <_ZN7MyChild4TestEv+0>: push %ebp
0x08048691 <_ZN7MyChild4TestEv+1>: mov %esp,%ebp
0x08048693 <_ZN7MyChild4TestEv+3>: sub $0x8,%esp
0x08048696 <_ZN7MyChild4TestEv+6>: movl $0x804875d,(%esp)
0x0804869d <_ZN7MyChild4TestEv+13>: call 0x8048440 <_init+36>
0x080486a2 <_ZN7MyChild4TestEv+18>: leave
0x080486a3 <_ZN7MyChild4TestEv+19>: ret
0x080486a4 <_ZN7MyChild4TestEv+20>: nop
0x080486a5 <_ZN7MyChild4TestEv+21>: nop
0x080486a6 <_ZN7MyChild4TestEv+22>: nop
0x080486a7 <_ZN7MyChild4TestEv+23>: nop
0x080486a8 <_ZN7MyChild4TestEv+24>: nop
0x080486a9 <_ZN7MyChild4TestEv+25>: nop
0x080486aa <_ZN7MyChild4TestEv+26>: nop
0x080486ab <_ZN7MyChild4TestEv+27>: nop
0x080486ac <_ZN7MyChild4TestEv+28>: nop
0x080486ad <_ZN7MyChild4TestEv+29>: nop
0x080486ae <_ZN7MyChild4TestEv+30>: nop
0x080486af <_ZN7MyChild4TestEv+31>: nop
End of assembler dump.
從這里我們分析MyChild::Test()的代碼:
0x08048690 <_ZN7MyChild4TestEv+0>: push %ebp
0x08048691 <_ZN7MyChild4TestEv+1>: mov %esp,%ebp
0x08048693 <_ZN7MyChild4TestEv+3>: sub $0x8,%esp
0x08048696 <_ZN7MyChild4TestEv+6>: movl $0x804875d,(%esp)
0x0804869d <_ZN7MyChild4TestEv+13>: call 0x8048440 <_init+36>
0x080486a2 <_ZN7MyChild4TestEv+18>: leave
0x080486a3 <_ZN7MyChild4TestEv+19>: ret
--------------------------------------------------------------------------
由于MyChild::Test()只是簡單地打印字符串,所以這里并沒有用到堆棧中的this指針,只是把字符
串常量的地址0x804875d入棧,然后打印。在調(diào)用call之前的寄存器和堆棧如下:
0xbfbfe7f0: 0x28070814 0xbfbfe804 0x2804fd26 0x28078040
0xbfbfe800: 0x0804875d 0xbfbfe844 0xbfbfe818 0x08048645
0xbfbfe810: 0xbfbfe848 0xbfbfe834 0xbfbfe858 0x080485ce
0xbfbfe820: 0xbfbfe848 0x00000001 0xbfbfe980 0xbfbfe988
0xbfbfe830: 0xbfbfe9a0 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0x08048780 0xbfbfe848
0xbfbfe850: 0xbfbfe870 0x00000001 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe848
5: /x $ebx = 0x1
4: /x $ecx = 0xbfbfe870
3: /x $edx = 0x8048690
2: /x $ebp = 0xbfbfe808
1: /x $esp = 0xbfbfe800
我們來看一下0x804875d的內(nèi)容:
(gdb) x /s 0x0804875d
0x804875d <_fini+97>: "Child::Test"
而這里調(diào)用的0x8048440是標準庫中的puts函 數(shù)。到這里為止,接下來的MyChild::Test的代碼就是
返回和nop指令了,我們直接從MyChild::Test返回到MyParent::DoJob的執(zhí)行中
從這里結(jié)束MyChild::Test()的代碼分析
--------------------------------------------------------------------------
由于MyParent::DoJob()剩下的也是返回和nop指令,這里直接返回到main中
從這里結(jié)束MyParent::DoJob的代碼分析
--------------------------------------------------------------------------
從MyParent::DoJob返回后,寄存器和堆棧的內(nèi)容如下:
0xbfbfe800: 0x0804875d 0xbfbfe844 0xbfbfe818 0x08048645
0xbfbfe810: 0xbfbfe848 0xbfbfe834 0xbfbfe858 0x080485ce
0xbfbfe820: 0xbfbfe848 0x00000001 0xbfbfe980 0xbfbfe988
0xbfbfe830: 0xbfbfe9a0 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0x08048780 0xbfbfe848
0xbfbfe850: 0xbfbfe870 0x00000001 0xbfbfe88c 0x08048529
6: /x $eax = 0xa
5: /x $ebx = 0x1
4: /x $ecx = 0xc
3: /x $edx = 0x0
2: /x $ebp = 0xbfbfe858
1: /x $esp = 0xbfbfe820
========================== 下面繼續(xù)代碼======================================
0x080485ce <main+46>: lea 0xfffffff0(%ebp),%eax
0x080485d1 <main+49>: mov %eax,(%esp)
0x080485d4 <main+52>: call 0x8048670 <_ZN7MyChildD1Ev>
--------------------------------------------------------------------------
執(zhí)行完DoJob后,現(xiàn)在整個main函數(shù)要結(jié)束,接下來首先調(diào)用的就是MyChild::~MyChild()。
這里傳入this指針到EAX,然后入棧調(diào)用MyChild的析構(gòu)函數(shù)。在調(diào)用析構(gòu)函數(shù)之前的寄存器和堆棧內(nèi)容如下:
0xbfbfe800: 0x0804875d 0xbfbfe844 0xbfbfe818 0x08048645
0xbfbfe810: 0xbfbfe848 0xbfbfe834 0xbfbfe858 0x080485ce
0xbfbfe820: 0xbfbfe848[1] 0x00000001 0xbfbfe980 0xbfbfe988
0xbfbfe830: 0xbfbfe9a0 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0x08048780 0xbfbfe848
0xbfbfe850: 0xbfbfe870 0x00000001 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe848
5: /x $ebx = 0x1
4: /x $ecx = 0xc
3: /x $edx = 0x0
2: /x $ebp = 0xbfbfe858
1: /x $esp = 0xbfbfe820
上面的[1]為入棧的this指針,MyChild::~MyChild()的代碼如下:
Dump of assembler code for function _ZN7MyChildD1Ev:
0x08048670 <_ZN7MyChildD1Ev+0>: push %ebp
0x08048671 <_ZN7MyChildD1Ev+1>: mov %esp,%ebp
0x08048673 <_ZN7MyChildD1Ev+3>: sub $0x8,%esp
0x08048676 <_ZN7MyChildD1Ev+6>: mov $0x8048780,%eax
0x0804867b <_ZN7MyChildD1Ev+11>: mov 0x8(%ebp),%edx
0x0804867e <_ZN7MyChildD1Ev+14>: mov %eax,(%edx)
0x08048680 <_ZN7MyChildD1Ev+16>: mov 0x8(%ebp),%eax
0x08048683 <_ZN7MyChildD1Ev+19>: mov %eax,(%esp)
0x08048686 <_ZN7MyChildD1Ev+22>: call 0x8048620 <_ZN8MyParentD2Ev>
0x0804868b <_ZN7MyChildD1Ev+27>: leave
0x0804868c <_ZN7MyChildD1Ev+28>: ret
0x0804868d <_ZN7MyChildD1Ev+29>: nop
0x0804868e <_ZN7MyChildD1Ev+30>: nop
0x0804868f <_ZN7MyChildD1Ev+31>: nop
End of assembler dump.
從這里開始分析MyChild::~MyChild()的代碼:
0x08048670 <_ZN7MyChildD1Ev+0>: push %ebp
0x08048671 <_ZN7MyChildD1Ev+1>: mov %esp,%ebp
0x08048673 <_ZN7MyChildD1Ev+3>: sub $0x8,%esp
0x08048676 <_ZN7MyChildD1Ev+6>: mov $0x8048780,%eax
0x0804867b <_ZN7MyChildD1Ev+11>: mov 0x8(%ebp),%edx
0x0804867e <_ZN7MyChildD1Ev+14>: mov %eax,(%edx)
0x08048680 <_ZN7MyChildD1Ev+16>: mov 0x8(%ebp),%eax
0x08048683 <_ZN7MyChildD1Ev+19>: mov %eax,(%esp)
0x08048686 <_ZN7MyChildD1Ev+22>: call 0x8048620 <_ZN8MyParentD2Ev>
--------------------------------------------------------------------------
上面的代碼分成三部分
1.建立SFP,保留8字節(jié)堆棧空間
2.設(shè)置MyChild的vtable到EAX,保存this指針到EDX,然后設(shè)置this指針的vptr指向MyChild的vtable
3.this指針入棧,調(diào)用MyParent::~MyParent()函數(shù)
在調(diào)用MyParent::~MyParent()之前的寄存器和堆棧內(nèi)容如下:
0xbfbfe800: 0x0804875d 0xbfbfe844 0xbfbfe818 0x08048645
0xbfbfe810: 0xbfbfe848 0xbfbfe834 0xbfbfe858 0x080485d9
0xbfbfe820: 0xbfbfe848 0x00000001 0xbfbfe980 0xbfbfe988
0xbfbfe830: 0xbfbfe9a0 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0x08048780[1] 0xbfbfe848
0xbfbfe850: 0xbfbfe870 0x00000001 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe848
5: /x $ebx = 0x1
4: /x $ecx = 0xc
3: /x $edx = 0xbfbfe848
2: /x $ebp = 0xbfbfe818
1: /x $esp = 0xbfbfe810
上面[1]為MyChild的vtable地址,驗證如下:
(gdb) x 0x08048780
0x8048780 <_ZTV7MyChild+8>: 0x08048690
(gdb) x 0x08048690
0x8048690 <_ZN7MyChild4TestEv>: 0x83e58955
經(jīng)過c++filt的還原:
_ZTV7MyChild+8 vtable for MyChild+8
_ZN7MyChild4TestEv MyChild::Test()
MyParent::~MyParent()的代碼如下:
Dump of assembler code for function _ZN8MyParentD2Ev:
0x08048620 <_ZN8MyParentD2Ev+0>: push %ebp
0x08048621 <_ZN8MyParentD2Ev+1>: mov %esp,%ebp
0x08048623 <_ZN8MyParentD2Ev+3>: mov $0x80487b8,%edx
0x08048628 <_ZN8MyParentD2Ev+8>: mov 0x8(%ebp),%eax
0x0804862b <_ZN8MyParentD2Ev+11>: mov %edx,(%eax)
0x0804862d <_ZN8MyParentD2Ev+13>: pop %ebp
0x0804862e <_ZN8MyParentD2Ev+14>: ret
0x0804862f <_ZN8MyParentD2Ev+15>: nop
End of assembler dump.
除了把vtable地址保存到EDX之后,再賦值給this指針的vptr,其他的什么也沒有做。
下面接著從MyChild::~MyChild()返回之后
==========================下面繼 續(xù)代碼======================================
0x080485d9 <main+57>: mov $0x0,%eax
0x080485de <main+62>: mov %eax,0xffffffe4(%ebp)
0x080485e1 <main+65>: jmp 0x8048602 <main+98>
0x080485e3 <main+67>: mov %eax,0xffffffe0(%ebp)
0x080485e6 <main+70>: mov 0xffffffe0(%ebp),%ebx
0x080485e9 <main+73>: lea 0xfffffff0(%ebp),%eax
0x080485ec <main+76>: mov %eax,(%esp)
0x080485ef <main+79>: call 0x8048670 <_ZN7MyChildD1Ev>
0x080485f4 <main+84>: mov %ebx,0xffffffe0(%ebp)
0x080485f7 <main+87>: mov 0xffffffe0(%ebp),%eax
0x080485fa <main+90>: mov %eax,(%esp)
0x080485fd <main+93>: call 0x8048480 <_init+100>
0x08048602 <main+98>: mov 0xffffffe4(%ebp),%eax
0x08048605 <main+101>: add $0x30,%esp
0x08048608 <main+104>: pop %ecx
0x08048609 <main+105>: pop %ebx
0x0804860a <main+106>: pop %ebp
0x0804860b <main+107>: lea 0xfffffffc(%ecx),%esp
0x0804860e <main+110>: ret
0x0804860f <main+111>: nop
End of assembler dump.
--------------------------------------------------------------------------
剩下的三部分代碼主要就是做出棧,異常處理和后繼的工作
1.上面執(zhí)行到"jmp 0x8048602 <main+98>"后就跳到第3部分
2.這塊調(diào)用_Unwind_Resume,屬于C++的異常處理部分
3.出棧處理,從main函數(shù)返回
4.進行-O2優(yōu)化后的代碼
代碼如下:
Dump of assembler code for function main:
0x080485a0 <main+0>: lea 0x4(%esp),%ecx
0x080485a4 <main+4>: and $0xfffffff0,%esp
0x080485a7 <main+7>: pushl 0xfffffffc(%ecx)
0x080485aa <main+10>: push %ebp
0x080485ab <main+11>: mov %esp,%ebp
0x080485ad <main+13>: push %ecx
0x080485ae <main+14>: sub $0x24,%esp
0x080485b1 <main+17>: lea 0xfffffff8(%ebp),%eax
0x080485b4 <main+20>: movl $0x80486b0,0xfffffff8(%ebp)
0x080485bb <main+27>: mov %eax,(%esp)
0x080485be <main+30>: call *0x80486b0
0x080485c4 <main+36>: add $0x24,%esp
0x080485c7 <main+39>: xor %eax,%eax
0x080485c9 <main+41>: pop %ecx
0x080485ca <main+42>: pop %ebp
0x080485cb <main+43>: lea 0xfffffffc(%ecx),%esp
0x080485ce <main+46>: ret
0x080485cf <main+47>: mov %eax,(%esp)
0x080485d2 <main+50>: call 0x8048480 <_init+100>
0x080485d7 <main+55>: nop
0x080485d8 <main+56>: nop
0x080485d9 <main+57>: nop
0x080485da <main+58>: nop
0x080485db <main+59>: nop
0x080485dc <main+60>: nop
0x080485dd <main+61>: nop
0x080485de <main+62>: nop
0x080485df <main+63>: nop
End of assembler dump.
下面開始分析代碼:
0x080485a0 <main+0>: lea 0x4(%esp),%ecx
0x080485a4 <main+4>: and $0xfffffff0,%esp
0x080485a7 <main+7>: pushl 0xfffffffc(%ecx)
0x080485aa <main+10>: push %ebp
0x080485ab <main+11>: mov %esp,%ebp
0x080485ad <main+13>: push %ecx
0x080485ae <main+14>: sub $0x24,%esp
--------------------------------------------------------------------------
程序開始,建立SFP,ECX入棧并保存36個字節(jié)空間后的寄存器和堆棧內(nèi)容如下:
0xbfbfe810: 0x00000001 0xbfbfe834 0x00000001 0x00000000
0xbfbfe820: 0x00000000 0x00000001 0xbfbfe984 0xbfbfe98c
0xbfbfe830: 0xbfbfe9a4 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe8a0 0x00000000 0xbfbfe868 0x2824a6b9
0xbfbfe850: 0x00000020 0xbfbfe870 0xbfbfe890 0x08048569
7: /x $eax = 0xbfbfe898
6: /x $ebx = 0x1
5: /x $ecx = 0xbfbfe870
3: /x $edx = 0x0
2: /x $ebp = 0xbfbfe858
1: /x $esp = 0xbfbfe830
==========================下面繼續(xù)代碼======================================
0x080485b1 <main+17>: lea 0xfffffff8(%ebp),%eax
0x080485b4 <main+20>: movl $0x80486b0,0xfffffff8(%ebp)
--------------------------------------------------------------------------
這段代碼把this指針保存到EAX中,再把MyChild的vtable直接賦值給[1](這里就是this指針)
的vptr空間。
寄存器和堆棧內(nèi)容如下:
0xbfbfe810: 0x00000001 0xbfbfe834 0x00000001 0x00000000
0xbfbfe820: 0x00000000 0x00000001 0xbfbfe980 0xbfbfe988
0xbfbfe830: 0xbfbfe9a0 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0xbfbfe868 0x2824a6b9
0xbfbfe850: 0x080486b0[1] 0xbfbfe870 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe850
5: /x $ebx = 0x1
4: /x $ecx = 0xbfbfe870
3: /x $edx = 0x0
2: /x $ebp = 0xbfbfe858
1: /x $esp = 0xbfbfe830
==========================下面繼續(xù)代碼======================================
0x080485bb <main+27>: mov %eax,(%esp)
0x080485be <main+30>: call *0x80486b0
--------------------------------------------------------------------------
這里this指針入棧后,并不通過vptr,直接調(diào)用vtable[0]的函數(shù),此時vptr形同虛設(shè),直接被
編譯器無視。驗證一下*0x80486b0 的處理函數(shù):
(gdb) x 0x80486b0
0x80486b0 <_ZTV7MyChild+8>: 0x080485e0
(gdb) x 0x080485e0
0x80485e0 <_ZN7MyChild4TestEv>: 0xc7e58955
進行還原后
_ZTV7MyChild+8 vtable for MyChild+8
_ZN7MyChild4TestEv MyChild::Test()
MyChild::Test()的代碼如下:
Dump of assembler code for function _ZN7MyChild4TestEv:
0x080485e0 <_ZN7MyChild4TestEv+0>: push %ebp
0x080485e1 <_ZN7MyChild4TestEv+1>: mov %esp,%ebp
0x080485e3 <_ZN7MyChild4TestEv+3>: movl $0x804868c,0x8(%ebp)
0x080485ea <_ZN7MyChild4TestEv+10>: pop %ebp
0x080485eb <_ZN7MyChild4TestEv+11>: jmp 0x8048440 <_init+36>
End of assembler dump.
從這里開始MyChild::Test()的代碼分析:
0x080485e0 <_ZN7MyChild4TestEv+0>: push %ebp
0x080485e1 <_ZN7MyChild4TestEv+1>: mov %esp,%ebp
0x080485e3 <_ZN7MyChild4TestEv+3>: movl $0x804868c,0x8(%ebp)
--------------------------------------------------------------------------
這里的"movl $0x804868c,0x8(%ebp)"直接破壞掉剛才入棧傳入的this指針,0x804868c
地址保存的就是我們要打印的字符串:
(gdb) x /s 0x0804868c
0x804868c <_fini+96>: "Child::Test"
==========================下面 繼續(xù)代碼======================================
0x080485ea <_ZN7MyChild4TestEv+10>: pop %ebp
0x080485eb <_ZN7MyChild4TestEv+11>: jmp 0x8048440 <_init+36>
--------------------------------------------------------------------------
保存字符串地址在堆棧中后,現(xiàn)在直接構(gòu)造puts函數(shù)調(diào)用之前的堆棧,由于每次在進入函數(shù)時都會執(zhí)行
"push %ebp"和"mov %esp, %ebp"而且每次讀取入?yún)⒍际峭ㄟ^0x8(%ebp)來讀取,所以這里必需要先
pop掉EBP,一但jmp進入puts函數(shù),執(zhí)行"push %ebp"和"mov %esp, %ebp"后,字符串地址存放的位
置剛好就是0x8(%ebp)。
pop %ebp之前的堆棧([1]為字符串地址):
0xbfbfe810: 0x00000001 0xbfbfe834 0x00000001 0x00000000
0xbfbfe820: 0x00000000 0x00000001 0xbfbfe858 0x080485c4
0xbfbfe830: 0x0804868c[1] 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0xbfbfe868 0x2824a6b9
0xbfbfe850: 0x080486b0 0xbfbfe870 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe850
5: /x $ebx = 0x1
4: /x $ecx = 0xbfbfe870
3: /x $edx = 0x0
2: /x $ebp = 0xbfbfe828
1: /x $esp = 0xbfbfe828
pop %ebp之后的堆棧,在jmp進入之前:
0xbfbfe810: 0x00000001 0xbfbfe834 0x00000001 0x00000000
0xbfbfe820: 0x00000000 0x00000001 0xbfbfe858 0x080485c4
0xbfbfe830: 0x0804868c[1] 0x2828cdc0 0xbfbfe858 0x00000001
0xbfbfe840: 0xbfbfe89c 0x00000000 0xbfbfe868 0x2824a6b9
0xbfbfe850: 0x080486b0 0xbfbfe870 0xbfbfe88c 0x08048529
6: /x $eax = 0xbfbfe850
5: /x $ebx = 0x1
4: /x $ecx = 0xbfbfe870
3: /x $edx = 0x0
2: /x $ebp = 0xbfbfe858
1: /x $esp = 0xbfbfe82c
在jmp進入puts函數(shù)后,打印出結(jié)果后程序就結(jié)束了。main函數(shù)中的下面的這部分代碼沒有執(zhí)行過
========================== 下面繼續(xù)代碼======================================
0x080485c4 <main+36>: add $0x24,%esp
0x080485c7 <main+39>: xor %eax,%eax
0x080485c9 <main+41>: pop %ecx
0x080485ca <main+42>: pop %ebp
0x080485cb <main+43>: lea 0xfffffffc(%ecx),%esp
0x080485ce <main+46>: ret
0x080485cf <main+47>: mov %eax,(%esp)
0x080485d2 <main+50>: call 0x8048480 <_init+100>
0x080485d7 <main+55>: nop
0x080485d8 <main+56>: nop
0x080485d9 <main+57>: nop
0x080485da <main+58>: nop
0x080485db <main+59>: nop
0x080485dc <main+60>: nop
0x080485dd <main+61>: nop
0x080485de <main+62>: nop
0x080485df <main+63>: nop
--------------------------------------------------------------------------
分析到此結(jié)束。
總結(jié):
用C++開發(fā)盡量使用優(yōu)化選項,虛擬函數(shù)的調(diào)用除了要多幾次給vptr賦值不同的vtable地址以外,效率和普通
函數(shù)是差不多的。類繼承深度越長導(dǎo)致的性能問題在于要執(zhí)行的基類的構(gòu)造函數(shù),需要初始化的數(shù)據(jù)成員越多,繼承鏈
越長,調(diào)用的構(gòu)造函數(shù)越多(inline后可以顯著提高這部分的效率)。
4.與優(yōu)化后C程序的對比
程序如下:
#include <stdio.h>
void Test();
int main()
{
Test();
}
void Test()
{
printf("Child::Test\n");
}
編譯使用-O2優(yōu)化,得到的匯編代碼:
main函數(shù):
Dump of assembler code for function main:
0x08048420 <main+0>: lea 0x4(%esp),%ecx
0x08048424 <main+4>: and $0xfffffff0,%esp
0x08048427 <main+7>: pushl 0xfffffffc(%ecx)
0x0804842a <main+10>: push %ebp
0x0804842b <main+11>: mov %esp,%ebp
0x0804842d <main+13>: push %ecx
0x0804842e <main+14>: sub $0x4,%esp
0x08048431 <main+17>: call 0x8048400 <Test>
0x08048436 <main+22>: add $0x4,%esp
0x08048439 <main+25>: pop %ecx
0x0804843a <main+26>: pop %ebp
0x0804843b <main+27>: lea 0xfffffffc(%ecx),%esp
0x0804843e <main+30>: ret
0x0804843f <main+31>: nop
End of assembler dump.
Test函數(shù):
Dump of assembler code for function Test:
0x08048400 <Test+0>: push %ebp
0x08048401 <Test+1>: mov %esp,%ebp
0x08048403 <Test+3>: sub $0x8,%esp
0x08048406 <Test+6>: movl $0x80484cc,(%esp)
0x0804840d <Test+13>: call 0x80482b8 <_init+36>
0x08048412 <Test+18>: leave
0x08048413 <Test+19>: ret
0x08048414 <Test+20>: lea 0x0(%esi),%esi
0x0804841a <Test+26>: lea 0x0(%edi),%edi
End of assembler dump.
單單從匯編代碼上面來看,C++的比C的多(異常處理的部分等等),C++使用jmp到打印函數(shù)的方法,而
C依然使用call的方式調(diào)用,在puts函數(shù)內(nèi)部的處理,C++也應(yīng)該做了一些特殊的處理(jmp進入,打印
完后直接退出程序,沒有執(zhí)行main函數(shù)中的出棧動作)。相比較于C而言,C++的優(yōu)化是另辟蹊徑了。
以上結(jié)果使用
GCC 4.2.1
GAS 2.15
GDB 6.1.1

