C++浅析——返回对象的函数
⼀、原码分析
1.1 测试代码
为了⽅便查看拷贝构造函数调⽤过程,⾃定义了拷贝构造函数,但啥也没⼲。
class CTEST
{
public:
int        m_nData;
//Method:
public:
CTEST()
{
printf("0x%p CTEST is constructed\n", this);
}
CTEST(CTEST& oCtest)
{
printf("0x%p CTEST copy constructor is called, copy object from 0x%p\n", this, &oCtest);
}
~CTEST()
{
printf("0x%p CTEST is destructed\n", this);
}
};
CTEST        GetCTest()
{
CTEST        oCtest;
return        oCtest;
}
int main(int argc, char* argv[])
{
printf("***************************Test1***************************\n\n");
CTEST oTest1 = GetCTest();
printf("oTest1 address is 0x%p\n", &oTest1);
printf("\n");
printf("***************************Test2***************************\n\n");
CTEST oTest2;
printf("oTest2 address is 0x%p\n", &oTest2);
oTest2 = GetCTest();
printf("\n");
printf("***************************Test3***************************\n\n");
GetCTest();
printf("\n");
getchar();
return 0;
}
运⾏结果
1.2 CTEST oTest1 = GetCTest();
⽤返回对象定义赋值对象时,oTest1的构造函数并不会被调⽤,⽽是传递其对象的指针作为隐含参数给GetCTest()函数,GetCTest()会在函数对象返回时调⽤其拷贝构造函数,利⽤返回对象对其初始化。
1.3 oTest2 = GetCTest();
⽤返回对象赋值对象时,与定义赋值不同,并不会传递其对象的指针给GetCTest()函数,⽽是产⽣了⼀个临时对象作为隐含参
数传递给GetCTest()函数,GetCTest()函数执⾏完毕后,利⽤临时对象给oTest2对象赋值(即浅拷贝,⽽不是调⽤其拷贝构造函数,如
果有资源指针,可能会造成资源泄露,有兴趣的朋友可以深⼊研究下这个问题)。
1.4 GetCTest();
单独调⽤GetCTest()函数和1.3类似,也会产⽣临时对象,只是调⽤结束后会析构掉。
⼆、深⼊分析
2.1 GetCTest()反汇编分析
7:    CTEST  GetCTest()
8:    {
9:        CTEST  oCtest;
00401074  lea        ecx,[ebp-10h]
00401077  call        @ILT+5(CTEST::CTEST) (0040100a)
0040107C  mov        dword ptr [ebp-4],1
10:
11:      return  oCtest;
00401083  lea        eax,[ebp-10h]
00401086  push        eax                                //压⼊oCtest对象指针
00401087  mov        ecx,dword ptr [ebp+8]                //取赋值对象的指针,该指针在调⽤GetCTest()函数时隐式传⼊
0040108A  call        @ILT+20(CTEST::CTEST) (00401019)        //调⽤赋值对象的拷贝构造函数
0040108F  mov        ecx,dword ptr [ebp-14h]
00401092  or          ecx,1
00401095  mov        dword ptr [ebp-14h],ecx
00401098  mov        byte ptr [ebp-4],0
0040109C  lea        ecx,[ebp-10h]
0040109F  call        @ILT+15(CTEST::~CTEST) (00401014) //返回对象oCtest析构
004010A4  mov        eax,dword ptr [ebp+8]                //返回赋值对象的指针
12:  }
通过以上反汇编代码的分析,可以看出GetCTest()函数在调⽤时编译器偷偷摸摸的传⼊了赋值对象的指针,⽽返回对象的函数实际上在返回时已经将返回对象析构了,其返回的是赋值对象的指针,只是在析构前利⽤返回对象其赋值对象进⾏拷贝构造了。
2.2 代码反汇编分析
17:      CTEST oTest1 = GetCTest();
0040123A  lea        eax,[ebp-10h]
0040123D  push        eax                                //压⼊oTest1的指针,以供GetCTest拷贝构造对象
0040123E  call        @ILT+0(GetCTest) (00401005)
00401243  add        esp,4
00401246  mov        dword ptr [ebp-4],0
18:      printf("oTest1 address is 0x%p\n", &oTest1);
0040124D  lea        ecx,[ebp-10h]
00401250  push        ecx
00401251  push        offset string "oTest1 address is 0x%p\n" (00427164)
00401256  call        printf (004018a0)
0040125B  add        esp,8
20:
21:      printf("***************************Test2***************************\n\n");
0040126B  push        offset string "***************************Test2" (00427114)
00401270  call        printf (004018a0)
00401275  add        esp,4
22:      CTEST oTest2;
00401278  lea        ecx,[ebp-14h]                        //调⽤oTest2的构造函数
0040127B  call        @ILT+5(CTEST::CTEST) (0040100a)
00401280  mov        byte ptr [ebp-4],1
23:      printf("oTest2 address is 0x%p\n", &oTest2);
00401284  lea        edx,[ebp-14h]
00401287  push        edx
00401288  push        offset string "oTest2 address is 0x%p\n" (004270f8)
0040128D  call        printf (004018a0)
00401292  add        esp,8
24:      oTest2 = GetCTest();
00401295  lea        eax,[ebp-1Ch]                        //压⼊临时对象的指针
00401298  push        eax
00401299  call        @ILT+0(GetCTest) (00401005)
0040129E  add        esp,4
004012A1  mov        dword ptr [ebp-28h],eax                //保存GetCTest返回的对象地址到[ebp-28h]
004012A4  mov        ecx,dword ptr [ebp-28h]
004012A7  mov        edx,dword ptr [ecx]                //拷贝GetCTest返回的对象的m_nData参数⾄oTest2对象的m_nData
004012A9  mov        dword ptr [ebp-14h],edx
printf函数是啥004012AC  lea        ecx,[ebp-1Ch]                        //临时对象析构
004012AF  call        @ILT+15(CTEST::~CTEST) (00401014)
26:
27:      printf("***************************Test3***************************\n\n");
004012C1  push        offset string "***************************Test3"... (004270ac)
004012C6  call        printf (004018a0)
004012CB  add        esp,4
28:      GetCTest();
004012CE  lea        eax,[ebp-20h]                        //压⼊临时对象的指针004012D1  push        eax
004012D2  call        @ILT+0(GetCTest) (00401005)
004012D7  add        esp,4
004012DA  lea        ecx,[ebp-20h]                        //临时对象析构004012DD  call        @ILT+15(CTEST::~CTEST) (00401014)