一.构造函数
构造函数可以让我们定义如何对类变量进行初始化,那么它们具体是如何工作的,不同的构造函数是否有不同的区别。以下实例定义了两者构造函数来初始化我们的类成员。
class Base
{
public:
Base()
{
x = 1;
y = 1;
}
Base(int x, int y)
{
this->x = x;
this->y = y;
}
private:
int x;
int y;
};
在main函数中的反汇编代码如下:
Base base1, base2(3, 4);
008910F8 lea ecx,[base1] //将base1的地址赋给ecx
008910FB call Base::Base (0891060h) //调用无参的构造函数
00891100 push 4
00891102 push 3 //压入两个实参
00891104 lea ecx,[base2] //将base2的地址赋给ecx
00891107 call Base::Base (0891000h) //调用构造函数
可以看到不同的构造函数在内存中的表现差别也只是参数的不同,所以定义多个构造函数和我们的函数重载是一样的,都是由编译器为我们生成了相应的代码供我们调用。那么函数内部的实现是如何。我们来分别看看0x0891060和0x0891000地址的内容
Base()
00891060 push ebp
00891061 mov ebp,esp
00891063 sub esp,0CCh
00891069 push ebx
0089106A push esi
0089106B push edi
0089106C push ecx //存储类变量地址
0089106D lea edi,[ebp-0CCh]
00891073 mov ecx,33h
00891078 mov eax,0CCCCCCCCh
0089107D rep stos dword ptr es:[edi]
0089107F pop ecx //恢复类变量地址
00891080 mov dword ptr [this],ecx //将类变量地址保存到局部变量中
00891083 mov ecx,offset _D3472036_test@cpp (0922008h)
00891088 call __CheckForDebuggerJustMyCode (0891330h)
{
x = 1;
0089108D mov eax,dword ptr [this] //取出类变量地址
00891090 mov dword ptr [eax],1 //地址偏移0处就是x的地址,赋值为1
y = 1;
00891096 mov eax,dword ptr [this] //取出类变量地址
00891099 mov dword ptr [eax+4],1 //地址偏移4处就是y的地址,赋值给1
}
008910A0 mov eax,dword ptr [this] //将类变量的地址赋给eax,作为返回值返回
008910A3 pop edi
008910A4 pop esi
008910A5 pop ebx
008910A6 add esp,0CCh
008910AC cmp ebp,esp
008910AE call _RTC_CheckEsp (08912F0h)
008910B3 mov esp,ebp
008910B5 pop ebp
008910B6 ret
Base(int x, int y)
00891000 push ebp
00891001 mov ebp,esp
00891003 sub esp,0CCh
00891009 push ebx
0089100A push esi
0089100B push edi
0089100C push ecx //保存类变量地址
0089100D lea edi,[ebp-0CCh]
00891013 mov ecx,33h
00891018 mov eax,0CCCCCCCCh
0089101D rep stos dword ptr es:[edi]
0089101F pop ecx //恢复类变量地址
00891020 mov dword ptr [this],ecx //将类变量地址给到局部变量
00891023 mov ecx,offset _D3472036_test@cpp (0922008h)
00891028 call __CheckForDebuggerJustMyCode (0891330h)
{
this->x = x;
0089102D mov eax,dword ptr [this] //取出类变量地址赋给eax
00891030 mov ecx,dword ptr [ebp+8] //取出参数1的内容
00891033 mov dword ptr [eax],ecx //地址偏移为0即使x的地址,将参数1的内容赋值给x
this->y = y;
00891035 mov eax,dword ptr [this] //取出类变量地址赋给eax
00891038 mov ecx,dword ptr [ebp+0Ch] //取出参数2的内容
0089103B mov dword ptr [eax+4],ecx //地址偏移为4即使y的地址,将参数2的内容赋值给y
}
0089103E mov eax,dword ptr [this] //将类变量的地址赋给eax,作为返回值返回
00891041 pop edi
00891042 pop esi
00891043 pop ebx
00891044 add esp,0CCh
0089104A cmp ebp,esp
0089104C call _RTC_CheckEsp (08912F0h)
00891051 mov esp,ebp
00891053 pop ebp
00891054 ret 8
可以看到构造函数和我们平时写的普通函数并没什么不一样,只不过在函数返回的时候构造函数会将类变量的地址作为返回值返回给用户。
二. 拷贝构造函数
拷贝构造函数作为一种特殊的构造函数在编程中有着广泛的用途,将上面的实例增加一个拷贝构造函数如下
class Base
{
public:
Base()
{
x = 1;
y = 1;
}
Base(int x, int y)
{
this->x = x;
this->y = y;
}
Base(const Base &obj)
{
this->x = obj.x;
this->y = obj.y;
}
private:
int x;
int y;
};
首先看看main函数中的反汇编
Base base1(3, 4);
00D310F8 push 4
00D310FA push 3
00D310FC lea ecx,[base1]
00D310FF call Base::Base (0D31060h) //调用有参构造函数
Base base2(base1);
00D31104 lea eax,[base1] //取出base1的变量地址
00D31107 push eax //将地址入栈
00D31108 lea ecx,[base2] //将base2的地址赋给ecx
00D3110B call Base::Base (0D31000h) //调用相应的拷贝构造函数
可以看到拷贝构造函数如预期一样把相应的类变量地址入栈后调用函数,接下来我们看看拷贝构造函数
Base(const Base &obj)
00D31000 push ebp
00D31001 mov ebp,esp
00D31003 sub esp,0CCh
00D31009 push ebx
00D3100A push esi
00D3100B push edi
00D3100C push ecx //保存类变量地址
00D3100D lea edi,[ebp-0CCh]
00D31013 mov ecx,33h
00D31018 mov eax,0CCCCCCCCh
00D3101D rep stos dword ptr es:[edi]
00D3101F pop ecx
00D31020 mov dword ptr [this],ecx //恢复类变量地址并赋值给局部变量
00D31023 mov ecx,offset _D3472036_test@cpp (0DC2008h)
00D31028 call __CheckForDebuggerJustMyCode (0D31340h)
{
this->x = obj.x;
00D3102D mov eax,dword ptr [this] //取出目标类变量地址ecx
00D31030 mov ecx,dword ptr [ebp+8] //取出参数,即源类变量地址赋给ecx
00D31033 mov edx,dword ptr [ecx] //此时ecx偏移0处地址就是源类变量的x,赋值给edx
00D31035 mov dword ptr [eax],edx //此时eax偏移为0地址就是目标类变量的x,将edx赋值给它
this->y = obj.y;
00D31037 mov eax,dword ptr [this]
00D3103A mov ecx,dword ptr [ebp+8]
00D3103D mov edx,dword ptr [ecx+4]
00D31040 mov dword ptr [eax+4],edx //与上述实现原理一样只是y是在偏移为4的地方
}
00D31043 mov eax,dword ptr [this] //将目标类变量地址作为返回值赋给eax
00D31046 pop edi
00D31047 pop esi
00D31048 pop ebx
00D31049 add esp,0CCh
00D3104F cmp ebp,esp
00D31051 call _RTC_CheckEsp (0D31300h)
00D31056 mov esp,ebp
00D31058 pop ebp
00D31059 ret 4
当然还有一种比较常见的是在类初始化赋值的时候会调用拷贝构造函数如下
Base base2 = base1;
00AA1104 lea eax,[base1]
00AA1107 push eax
00AA1108 lea ecx,[base2]
00AA110B call Base::Base (0AA1000h)
反汇编的内容跟上面传参调用是一样的,可见由拷贝构造函数的时候,初始化的时候编译器会帮我们自动调用拷贝构造函数。
三.析构函数
在C/C++中,析构函数常常被用来释放内存空间,修改上述实例为如下
class Base
{
public:
Base()
{
pTest = malloc(10);
}
Base(int size)
{
pTest = malloc(size);
}
~Base()
{
printf("~Base()\n");
if (pTest) free(pTest);
}
private:
void *pTest;
};
接着我将会在test函数中定义这个变量,依次来观察析构函数的使用
void test()
{
008E10F0 push ebp
008E10F1 mov ebp,esp
008E10F3 sub esp,0CCh
008E10F9 push ebx
008E10FA push esi
008E10FB push edi
008E10FC lea edi,[ebp-0CCh]
008E1102 mov ecx,33h
008E1107 mov eax,0CCCCCCCCh
008E110C rep stos dword ptr es:[edi]
008E110E mov ecx,offset _D3472036_test@cpp (097200Bh)
008E1113 call __CheckForDebuggerJustMyCode (08E14C0h)
Base base1(10);
008E1118 push 0Ah
008E111A lea ecx,[base1]
008E111D call Base::Base (08E1000h) //调用构造函数
}
008E1122 lea ecx,[base1] //将base1的地址赋给ecx
008E1125 call Base::~Base (08E1060h) //调用析构函数
008E112A push edx //开始执行函数推出的代码
008E112B mov ecx,ebp
008E112D push eax
008E112E lea edx,ds:[8E1150h]
008E1134 call _RTC_CheckStackVars (08E1410h)
008E1139 pop eax
008E113A pop edx
008E113B pop edi
008E113C pop esi
008E113D pop ebx
008E113E add esp,0CCh
008E1144 cmp ebp,esp
008E1146 call _RTC_CheckEsp (08E1480h)
008E114B mov esp,ebp
008E114D pop ebp
008E114E ret
由上可以看出,对于局部变量来说,在函数退出之前会首先调用相应的析构函数释放内存,随后才开始函数退出代码的执行。再看析构函数内容
~Base()
{
008E1060 push ebp
008E1061 mov ebp,esp
008E1063 push 0FFFFFFFFh
008E1065 push 952990h
008E106A mov eax,dword ptr fs:[00000000h]
008E1070 push eax
008E1071 mov dword ptr fs:[0],esp
008E1078 sub esp,0CCh
008E107E push ebx
008E107F push esi
008E1080 push edi
008E1081 push ecx //保存类变量地址
008E1082 lea edi,[ebp-0D8h]
008E1088 mov ecx,33h
008E108D mov eax,0CCCCCCCCh
008E1092 rep stos dword ptr es:[edi]
008E1094 pop ecx //恢复类变量地址
008E1095 mov dword ptr [this],ecx //将类变量地址赋给局部变量
008E1098 mov ecx,offset _D3472036_test@cpp (097200Bh)
008E109D call __CheckForDebuggerJustMyCode (08E14C0h)
printf("~Base()\n");
008E10A2 push offset string "~Base()\n" (09531B0h)
008E10A7 call printf (08E1280h) //打印输出
008E10AC add esp,4
if (pTest) free(pTest);
008E10AF mov eax,dword ptr [this] //取出类变量地址
008E10B2 cmp dword ptr [eax],0 //判断地址中的变量值是否为0
008E10B5 je Base::~Base+65h (08E10C5h) //为0则跳转
008E10B7 mov eax,dword ptr [this] //取出类变量地址
008E10BA mov ecx,dword ptr [eax] //将类变量地址中的内容取出赋给ecx
008E10BC push ecx //此时ecx便是pTest
008E10BD call free (091A2A0h) //调用free函数
008E10C2 add esp,4
}
008E10C5 mov ecx,dword ptr [ebp-0Ch]
008E10C8 mov dword ptr fs:[0],ecx
008E10CF pop edi
008E10D0 pop esi
008E10D1 pop ebx
008E10D2 add esp,0D8h
008E10D8 cmp ebp,esp
008E10DA call _RTC_CheckEsp (08E1480h)
008E10DF mov esp,ebp
008E10E1 pop ebp
008E10E2 ret
可以看到析构函数的执行和我们的普通成员函数的执行是一样的,它并没有把函数地址作为返回值返回给用户。
[培训]《安卓高级研修班(网课)》月薪三万计划
最后于 2021-10-20 11:24
被1900编辑
,原因: