从逆向分析角度看C++拷贝构造函数

一段C++代码:

//: HowMany_2.cpp
#include <iostream>

using namespace std;

class HowMany {
  static int objectCount;
  
public:
  HowMany() {
    ++objectCount;
    print("HowMany()");
  }
  ~HowMany() {
    --objectCount;
    print("~HowMany()");
  }
  HowMany(const HowMany& h) {
    ++objectCount;
    print("HowMany(const HowMany&)");
  }
  
  void print(const char ss[]) {
    cout << ss << ": ";
    cout << "objectCount = " << objectCount << endl;
    return ;
  }
};

int HowMany::objectCount = 0;

HowMany f(HowMany x) {
  x.print("x argument inside f()");
  cout << "Return From f()" << endl;
  return x;  // 有返回值 x
}

int main() {
  {
    HowMany h;
    cout << "Entering f()" << endl;
    HowMany h2 = f(h);
  }
  return 0;
} ///:~

运行结果:


 

Assembly Code:

38:   int main() {
39:     {
40:       HowMany h;
004017FD   lea         ecx,[h]					; [h] 为对象 h 的内存地址
00401800   call        @ILT+685(HowMany::HowMany) (004012b2)	; 调用构造函数
00401805   mov         dword ptr [ebp-4],0
41:       cout << "Entering f()" << endl;
0040180C   push        offset @ILT+200(std::endl) (004010cd)
00401811   push        offset string "Entering f()" (0046f090)
00401816   push        offset std::cout (0047ce98)
0040181B   call        @ILT+660(std::operator<<) (00401299)	; 题外话,观察一下进栈顺序
00401820   add         esp,8
00401823   mov         ecx,eax
00401825   call        @ILT+480(std::basic_ostream<char,std::char_traits<char> >::operator<<) (004011e5)
42:       HowMany h2 = f(h);
0040182A   push        ecx
0040182B   mov         ecx,esp					; 当前 ESP 所指的栈块作为临时对象Temp的内存地址
0040182D   mov         dword ptr [ebp-18h],esp
00401830   lea         eax,[h]					
00401833   push        eax					; 将h的内存地址[h]压入堆栈
00401834   call        @ILT+0(HowMany::HowMany) (00401005)	; 调用拷贝构造函数,把h的内容拷贝到Temp的内存中
00401839   mov         dword ptr [ebp-1Ch],eax
0040183C   lea         ecx,[h2]	
0040183F   push        ecx					; 将h2的内存地址[h2]压入堆栈
00401840   call        @ILT+640(f) (00401285)			; 调用f()函数
00401845   add         esp,8
00401848   mov         dword ptr [ebp-20h],eax
43:     }
0040184B   lea         ecx,[h2]
0040184E   call        @ILT+500(HowMany::~HowMany) (004011f9)	; 调用析构函数,销毁h2
00401853   mov         dword ptr [ebp-4],0FFFFFFFFh
0040185A   lea         ecx,[h]
0040185D   call        @ILT+500(HowMany::~HowMany) (004011f9)	; 调用析构函数,销毁h
44:     // getchar();
45:     return 0;
00401862   xor         eax,eax
46:   } ///:~



fun()函数的工作机制:

32:   HowMany f(HowMany x) {
33:     x.print("x argument inside f()");
004015DB   push        offset string "x argument inside f()" (0046f030)
004015E0   lea         ecx,[ebp+0Ch]
004015E3   call        @ILT+575(HowMany::print) (00401244)
34:     cout << "Return From f()" << endl;
004015E8   push        offset @ILT+200(std::endl) (004010cd)
004015ED   push        offset string "Return From f()" (0046f01c)
004015F2   push        offset std::cout (0047ce98)
004015F7   call        @ILT+660(std::operator<<) (00401299)
004015FC   add         esp,8
004015FF   mov         ecx,eax
00401601   call        @ILT+480(std::basic_ostream<char,std::char_traits<char> >::operator<<) (004011e5)
35:     return x;
00401606   lea         eax,[ebp+0Ch]				; [ebp+0C]为Temp的内存地址
00401609   push        eax
0040160A   mov         ecx,dword ptr [ebp+8]			; [ebp+8]为h2的内存地址,ecx指向h2内存块
0040160D   call        @ILT+0(HowMany::HowMany) (00401005)	; 调用拷贝构造函数,将Temp的内容拷贝到h2的内存中
00401612   mov         ecx,dword ptr [ebp-10h]
00401615   or          ecx,1
00401618   mov         dword ptr [ebp-10h],ecx
0040161B   mov         byte ptr [ebp-4],0
0040161F   lea         ecx,[ebp+0Ch]				; ecx保存Temp的内存地址
00401622   call        @ILT+500(HowMany::~HowMany) (004011f9)	; 调用析构函数,销毁Temp
00401627   mov         eax,dword ptr [ebp+8]			; eax保存h2的内存地址
36:   }

对于运行结果


解析如下:

1. 对象h调用构造函数

2. 输出字符串“Entering f()”

3. 在进入f()函数之前,创建了一个临时对象Temp,调用拷贝构造函数,将对象h的内容拷贝到Temp中

4. 进入f()函数,在执行“return x”语句后,调用拷贝构造函数,将对象Temp的内容拷贝到h2中(完成了h2 = f(h)的工作)

5. 在f()函数结束前,为Temp调用析构函数,销毁Temp对象

6. 退出f()函数,在main函数结束前,先销毁对象h2,最后销毁对象h

----------------------------------------------------------------------------

逆向分析时执行“HowMany h2 = f(h);”语句过程中栈分布的记录:

*****

进入f()函数前(创建了一个临时对象Temp,调用拷贝构造函数,将对象h的内容拷贝到Temp中



进入f()函数后



*********************************************************

再看<<Thinking in C++>>中的一段代码,更为清晰的讲解了拷贝构造函数的机制:

//: HowMany_2.cpp
#include <string>
#include <iostream>

using namespace std;

class HowMany {
  string name;
  static int objectCount;
  
public:
  HowMany(const string& id = "") {
    name = id;
    ++objectCount;
    print("HowMany()");
  }
  ~HowMany() {
    --objectCount;
    print("~HowMany()");
  }
  HowMany(const HowMany& h) {
    name = h.name + "copy";
    ++objectCount;
    print("HowMany(const HowMany&)");
  }
  
  void print(const string& msg = "") {
    if (msg.length() != 0) {
      cout << msg << endl;
    }
    cout << '\t' << name << ": "
		 << "objectCount = " << objectCount << endl;
    
    return ;
  }
};

int HowMany::objectCount = 0;

HowMany f(HowMany x) {
  x.print("x argument inside f()");
  cout << "Return From f()" << endl;
  return x;
}

int main() {
  {
    HowMany h("h");
    cout << "Entering f()" << endl;
    HowMany h2 = f(h);
    cout << "Call f(), no return value" << endl;
    f(h);
    cout << "After call to f()" << endl;
  }
  // getchar();
  return 0;
} ///:~

运行结果:


解释如下:

1. 创建对象h,并调用构造函数

2. 输出“Entering f()”字符串

3. 进入f()函数前,创建一个临时对象Temp,并调用拷贝构造函数,将对象h的内容拷贝到Temp中

4. 进入f()函数后,在“return x”时,调用拷贝构造函数,将对象Temp的内容拷贝到h2中,在函数结束前调用析构函数,销毁对象Temp

5. 输出字符串

6.进入f()函数前,创建一个临时对象Temp,并调用拷贝构造函数,将对象h的内容拷贝到Temp中

7. 进入f()函数后,创建一个临时对象x,在“return x”时,调用拷贝构造函数,将对象Temp的内容拷贝到x中,在函数结束前调用析构函数,先销毁对象Temp,再销毁对象x

8. 在main函数结束前,调用析构函数,先销毁对象h2,再销毁对象h

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值