用汇编的眼光看C++(之类继承)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
继承是类的一个基本属性,可是在类的继承过程中,函数是怎么初始化?怎么析构的呢?我们不妨看看下面这样的一段代码?
- class employee
- {
- public:
- employee() { printf("employee()!\n");}
- ~employee() { printf("~employee()!\n");}
- };
-
- class manager : public employee
- {
- public:
- manager() { printf("manager()!\n");}
- ~manager() { printf("~maneger()!\n");}
- };
class employee
{
public:
employee() { printf("employee()!\n");}
~employee() { printf("~employee()!\n");}
};
class manager : public employee
{
public:
manager() { printf("manager()!\n");}
~manager() { printf("~maneger()!\n");}
};
看到上面的代码,相信大家也明白了,我们定义了这样一个类。基类是empoyee,继承类是manager。我们看到manager是一种特殊的employee,那么在内存构建和析构的时候函数又是怎么安排的呢?
- 74: manager m;
- 00401268 lea ecx,[ebp-4]
- 0040126B call @ILT+60(manager::manager) (00401041)
- 75: }
- 00401270 lea ecx,[ebp-4]
- 00401273 call @ILT+0(manager::~manager) (00401005)
- 00401278 pop edi
- 00401279 pop esi
- 0040127A pop ebx
- 0040127B add esp,44h
- 0040127E cmp ebp,esp
- 00401280 call __chkesp (00408760)
- 00401285 mov esp,ebp
- 00401287 pop ebp
74: manager m;
00401268 lea ecx,[ebp-4]
0040126B call @ILT+60(manager::manager) (00401041)
75: }
00401270 lea ecx,[ebp-4]
00401273 call @ILT+0(manager::~manager) (00401005)
00401278 pop edi
00401279 pop esi
0040127A pop ebx
0040127B add esp,44h
0040127E cmp ebp,esp
00401280 call __chkesp (00408760)
00401285 mov esp,ebp
00401287 pop ebp
我们发现manager的构造和析构其实也简单。构造函数其实就是在变量出现的时候进行构造。那什么时候析构呢?也就在函数快结束的时候进行析构。下面我们可以进一步讨论在manager的构造和析构究竟是怎么做的?
- 65: class manager : public employee
- 66: {
- 67: public:
- 68: manager() { printf("manager()!\n");}
- 004012A0 push ebp
- 004012A1 mov ebp,esp
- 004012A3 sub esp,44h
- 004012A6 push ebx
- 004012A7 push esi
- 004012A8 push edi
- 004012A9 push ecx
- 004012AA lea edi,[ebp-44h]
- 004012AD mov ecx,11h
- 004012B2 mov eax,0CCCCCCCCh
- 004012B7 rep stos dword ptr [edi]
- 004012B9 pop ecx
- 004012BA mov dword ptr [ebp-4],ecx
- 004012BD mov ecx,dword ptr [ebp-4]
- 004012C0 call @ILT+40(employee::employee) (0040102d)
- 004012C5 push offset string "manager()!\n" (00431020)
- 004012CA call printf (004086e0)
- 004012CF add esp,4
- 004012D2 mov eax,dword ptr [ebp-4]
- 004012D5 pop edi
- 004012D6 pop esi
- 004012D7 pop ebx
- 004012D8 add esp,44h
- 004012DB cmp ebp,esp
- 004012DD call __chkesp (00408760)
- 004012E2 mov esp,ebp
- 004012E4 pop ebp
- 004012E5 ret
65: class manager : public employee
66: {
67: public:
68: manager() { printf("manager()!\n");}
004012A0 push ebp
004012A1 mov ebp,esp
004012A3 sub esp,44h
004012A6 push ebx
004012A7 push esi
004012A8 push edi
004012A9 push ecx
004012AA lea edi,[ebp-44h]
004012AD mov ecx,11h
004012B2 mov eax,0CCCCCCCCh
004012B7 rep stos dword ptr [edi]
004012B9 pop ecx
004012BA mov dword ptr [ebp-4],ecx
004012BD mov ecx,dword ptr [ebp-4]
004012C0 call @ILT+40(employee::employee) (0040102d)
004012C5 push offset string "manager()!\n" (00431020)
004012CA call printf (004086e0)
004012CF add esp,4
004012D2 mov eax,dword ptr [ebp-4]
004012D5 pop edi
004012D6 pop esi
004012D7 pop ebx
004012D8 add esp,44h
004012DB cmp ebp,esp
004012DD call __chkesp (00408760)
004012E2 mov esp,ebp
004012E4 pop ebp
004012E5 ret
我们发现,manager的构造里面添加了employee的缺省构造函数,那么析构函数呢?
- 69: ~manager() { printf("~maneger()!\n");}
- 00401350 push ebp
- 00401351 mov ebp,esp
- 00401353 sub esp,44h
- 00401356 push ebx
- 00401357 push esi
- 00401358 push edi
- 00401359 push ecx
- 0040135A lea edi,[ebp-44h]
- 0040135D mov ecx,11h
- 00401362 mov eax,0CCCCCCCCh
- 00401367 rep stos dword ptr [edi]
- 00401369 pop ecx
- 0040136A mov dword ptr [ebp-4],ecx
- 0040136D push offset string "~maneger()!\n" (00431040)
- 00401372 call printf (004086e0)
- 00401377 add esp,4
- 0040137A mov ecx,dword ptr [ebp-4]
- 0040137D call @ILT+5(employee::~employee) (0040100a)
- 00401382 pop edi
- 00401383 pop esi
- 00401384 pop ebx
- 00401385 add esp,44h
- 00401388 cmp ebp,esp
- 0040138A call __chkesp (00408760)
- 0040138F mov esp,ebp
- 00401391 pop ebp
- 00401392 ret
69: ~manager() { printf("~maneger()!\n");}
00401350 push ebp
00401351 mov ebp,esp
00401353 sub esp,44h
00401356 push ebx
00401357 push esi
00401358 push edi
00401359 push ecx
0040135A lea edi,[ebp-44h]
0040135D mov ecx,11h
00401362 mov eax,0CCCCCCCCh
00401367 rep stos dword ptr [edi]
00401369 pop ecx
0040136A mov dword ptr [ebp-4],ecx
0040136D push offset string "~maneger()!\n" (00431040)
00401372 call printf (004086e0)
00401377 add esp,4
0040137A mov ecx,dword ptr [ebp-4]
0040137D call @ILT+5(employee::~employee) (0040100a)
00401382 pop edi
00401383 pop esi
00401384 pop ebx
00401385 add esp,44h
00401388 cmp ebp,esp
0040138A call __chkesp (00408760)
0040138F mov esp,ebp
00401391 pop ebp
00401392 ret
我们发现,manager构造的时候employee率先构造,然后打印manager;析构的时候,恰恰相反,manager首先析构自己,然后在再调用employee的析构函数,上面的汇编代码证明了一切。
用汇编的眼光看C++(之虚函数)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
虚函数是面向对象设计中的一个重要内容。它的出现使得我们只需要相同的接口函数,并可以得到不同的生成结果。但是有些朋友却知其然,不知其所以然,为什么会出现这样的结果,我们可以用一段代码说明问题。首先,我们先定义两个基本类型,一个是employee,一个是manager,看过前面一片博客的朋友应该都有点印象:
- class employee
- {
- public:
- employee() { }
- ~employee() {}
- virtual void print() const { printf("employee!\n");}
- };
- class manager : public employee
- {
- public:
- manager() {}
- ~manager() {}
- void print() const {printf("manager!\n");}
- };
class employee
{
public:
employee() { }
~employee() {}
virtual void print() const { printf("employee!\n");}
};
class manager : public employee
{
public:
manager() {}
~manager() {}
void print() const {printf("manager!\n");}
};
我们看到,和前面出现的成员函数稍微有一些不同,这里的print函数之前出现了virtual。然而正是这个virtual发挥了巨大的作用。可以毫不夸张地说,没有虚函数,基本上就没有设计模式,也就无法体现C++语言在面向对象设计中的巨大优越性。下面我们看看这个virtual是怎样发挥作用的?
- 76: employee p;
- 0040128D lea ecx,[ebp-10h]
- 00401290 call @ILT+45(employee::employee) (00401032)
- 00401295 mov dword ptr [ebp-4],0
- 77: manager m;
- 0040129C lea ecx,[ebp-14h]
- 0040129F call @ILT+65(manager::manager) (00401046)
- 004012A4 mov byte ptr [ebp-4],1
- 78: employee* e = &p;
- 004012A8 lea eax,[ebp-10h]
- 004012AB mov dword ptr [ebp-18h],eax
- 79: e->print();
- 004012AE mov ecx,dword ptr [ebp-18h]
- 004012B1 mov edx,dword ptr [ecx]
- 004012B3 mov esi,esp
- 004012B5 mov ecx,dword ptr [ebp-18h]
- 004012B8 call dword ptr [edx]
- 004012BA cmp esi,esp
- 004012BC call __chkesp (00408870)
- 80: e = &m;
- 004012C1 lea eax,[ebp-14h]
- 004012C4 mov dword ptr [ebp-18h],eax
- 81: e->print();
- 004012C7 mov ecx,dword ptr [ebp-18h]
- 004012CA mov edx,dword ptr [ecx]
- 004012CC mov esi,esp
- 004012CE mov ecx,dword ptr [ebp-18h]
- 004012D1 call dword ptr [edx]
- 004012D3 cmp esi,esp
- 004012D5 call __chkesp (00408870)
- 82: }
76: employee p;
0040128D lea ecx,[ebp-10h]
00401290 call @ILT+45(employee::employee) (00401032)
00401295 mov dword ptr [ebp-4],0
77: manager m;
0040129C lea ecx,[ebp-14h]
0040129F call @ILT+65(manager::manager) (00401046)
004012A4 mov byte ptr [ebp-4],1
78: employee* e = &p;
004012A8 lea eax,[ebp-10h]
004012AB mov dword ptr [ebp-18h],eax
79: e->print();
004012AE mov ecx,dword ptr [ebp-18h]
004012B1 mov edx,dword ptr [ecx]
004012B3 mov esi,esp
004012B5 mov ecx,dword ptr [ebp-18h]
004012B8 call dword ptr [edx]
004012BA cmp esi,esp
004012BC call __chkesp (00408870)
80: e = &m;
004012C1 lea eax,[ebp-14h]
004012C4 mov dword ptr [ebp-18h],eax
81: e->print();
004012C7 mov ecx,dword ptr [ebp-18h]
004012CA mov edx,dword ptr [ecx]
004012CC mov esi,esp
004012CE mov ecx,dword ptr [ebp-18h]
004012D1 call dword ptr [edx]
004012D3 cmp esi,esp
004012D5 call __chkesp (00408870)
82: }
上面是一段函数调用的代码,代码可以稍微有点长。不过没有关系,我们可以按照代码的行数一行一行地去进行说明和理解。
76行: 我们创建了employee类型的一个变量p,这个可以从后面的employee的构造函数可以看出来
77行: 我们创建了manager类型的一个变量,这个也可以从后面的manager的构造函数看出
78行: 我们创建一个指针临时变量e,它保存了变量p的地址,这一句也比较简单
79行: 我们发现79句下面共有7句汇编,其中第三句、第六句、第七句是平衡堆栈的时候用的,和我们的调用没有关系。那么call的edx是什么东西呢?原来函数调用的顺序是这样的:edx -> [ecx] ->[ebp-0x18],不知道大家看明白了没有。在内存的第一个字节记录一个指向print函数指针的指针,也就是edx。通过这个edx,我们就可以查找到位于edx地址的内容是什么。后来我们提取出来后发现[edx]的内容正是我们要查找的print函数地址。这里相当于一个二次寻址的过程。
80行: 我们重新对临时变量e进行了赋值,此时e保存的是变量m的地址
81行: 我们发现此时的寻找过程和79行惊奇地一致,原因就在于edx的内容不同罢了。也就是指向函数指针的指针发生了变化而已。
试想一下,如果没有这个virtual函数,以上这段代码会发生什么差别呢?
- 76: employee p;
- 0040127D lea ecx,[ebp-10h]
- 00401280 call @ILT+45(employee::employee) (00401032)
- 00401285 mov dword ptr [ebp-4],0
- 77: manager m;
- 0040128C lea ecx,[ebp-14h]
- 0040128F call @ILT+65(manager::manager) (00401046)
- 00401294 mov byte ptr [ebp-4],1
- 78: employee* e = &p;
- 00401298 lea eax,[ebp-10h]
- 0040129B mov dword ptr [ebp-18h],eax
- 79: e->print();
- 0040129E mov ecx,dword ptr [ebp-18h]
- 004012A1 call @ILT+5(employee::print) (0040100a)
- 80: e = &m;
- 004012A6 lea ecx,[ebp-14h]
- 004012A9 mov dword ptr [ebp-18h],ecx
- 81: e->print();
- 004012AC mov ecx,dword ptr [ebp-18h]
- 004012AF call @ILT+5(employee::print) (0040100a)
- 82: }
76: employee p;
0040127D lea ecx,[ebp-10h]
00401280 call @ILT+45(employee::employee) (00401032)
00401285 mov dword ptr [ebp-4],0
77: manager m;
0040128C lea ecx,[ebp-14h]
0040128F call @ILT+65(manager::manager) (00401046)
00401294 mov byte ptr [ebp-4],1
78: employee* e = &p;
00401298 lea eax,[ebp-10h]
0040129B mov dword ptr [ebp-18h],eax
79: e->print();
0040129E mov ecx,dword ptr [ebp-18h]
004012A1 call @ILT+5(employee::print) (0040100a)
80: e = &m;
004012A6 lea ecx,[ebp-14h]
004012A9 mov dword ptr [ebp-18h],ecx
81: e->print();
004012AC mov ecx,dword ptr [ebp-18h]
004012AF call @ILT+5(employee::print) (0040100a)
82: }
很遗憾,这里就没有了动态查找的过程,所有的打印函数最终都指向了函数employee::print,此时多态性也不复存在了。
【预告: 下一片博客将介绍类中的静态变量和静态函数】