C++逆向分析——对象拷贝
对象拷贝
我们通常存储对象,都用数组、列表之类的来存储,那如下所示我们使用数组来存储对象,但是在工作中发现这个数组不够用了,就需要一个更大的数据,但我们重新创建一个数组还需要把原来的数据复制过来;在C语言中可以使用函数来进行拷贝,直接拷贝内存,在C++中实际上跟C语言要做的事情是一样的,在C++中就称之为对象拷贝。
拷贝构造函数
如何在C++中拷贝构造函数,来看一下如下代码:
可以看见我们定义了一个类,然后创建了两个对象obj、objNew,但是第二个对象的语法很奇怪,传入的参数是第一对象,其实这就是默认拷贝析构函数。
其本质就是很简单的内存复制:
在vs2022里实验下:
#include <stdio.h> struct Student { int a; int b; Student() { printf("Look."); } Student(int a, int b) { this->a = a; this->b = b; } ~Student() { printf("Look A."); } }; void main() { Student s(1, 2); Student s2(s); return; }
反汇编码可以看到,的确就是无脑式的内存copy!
Student s(1, 2); 00FE458F 6A 02 push 2 00FE4591 6A 01 push 1 00FE4593 8D 4D F0 lea ecx,[s] 00FE4596 E8 2A CE FF FF call Student::Student (0FE13C5h) Student s2(s); 00FE459B 8B 45 F0 mov eax,dword ptr [s] 00FE459E 8B 4D F4 mov ecx,dword ptr [ebp-0Ch] 00FE45A1 89 45 E0 mov dword ptr [s2],eax 00FE45A4 89 4D E4 mov dword ptr [ebp-1Ch],ecx return;
在odb里看到的内存copy示例(我觉得奇怪的就是为啥不是连续4个地址变量呢?):
上面的内存复制是在栈中,而我们想在堆中去拷贝可以这样写:
CObject* p =
new
CObject(obj);
如上所示,我们是通过拷贝析构函数在内存中创建了一个新的对象,而如果该类本身有一个父类,父类会被拷贝吗?我们写一段代码来论证一下:
那么这段代码,拷贝构造函数不仅可以将当前对象的内容复制,还可以将父类的内容复制过来。
拷贝构造函数是编译器已经提供的,其已经非常成熟了,通常情况下是不建议自己写拷贝构造函数的,除非出现类中包含指针类型的成员,这种情况是需要自己重些拷贝构造函数的,因为拷贝构造函数只是会拷贝当前指针成员的值,并不会拷贝指针成员指向的内容。
所以这种拷贝方式,我们可以称之为浅拷贝;而如果可以做到能够复制成员的情况下,还可以将指针指向的内存地址复制过来,并自动申请一块新的内存提供,这种方式我们称之为深拷贝。
如果要实现深拷贝,我们就需要自己重写拷贝构造函数。
MyObject(
const
MyObject& obj) {
m_nLength = obj.m_nLength;
m_strBuffer =
new
char
[m_nLength];
memset(m_strBuffer,
0
, m_nLength);
strcpy(m_strBuffer, obj.m_strBuffer);
}
需要注意的是这个格式是固定的,不要自己「自由发挥」。
总结:
-
如果不需要深拷贝,就不要自己添加拷贝构造函数;
-
如果你天假了拷贝构造函数,那么编译器将不再提供,所有的事情都需要由新添加的函数自己来处理。
重载赋值运算符
上一章学习了如何通过拷贝对象函数的方式来实现对象拷贝,这里就来学习使用重载赋值运算符实现对象拷贝。
在C++中是允许我们在两个对象之间直接使用赋值运算符的:
CObject a(
1
,
2
), b(
3
,
4
);
b = a;
那么赋值运算符实现的对象复制是否会当前复制对象继承的父类进行复制呢?
如上图所示,赋值运算符是可以复制父类的。
但是在这里赋值运算符是否就非常完美了呢?不是的,赋值运算符和拷贝构造函数是有相同缺点的,那就是其默认都是浅拷贝。
我们想要解决这个问题就需要重写一个赋值运算符,自己来实现深拷贝。