当前位置:天才代写 > tutorial > C语言/C++ 教程 > C++工具机关及多态之虚成员函数挪用

C++工具机关及多态之虚成员函数挪用

2017-11-04 08:00 星期六 所属: C语言/C++ 教程 浏览:462

副标题#e#

在结构函数中挪用虚成员函数,固然这是个不很常用的技能,但研究一下可以加深对虚函数机制及工具结构进程的领略。这个问题也和一般直观上的认识有所差别。先看看下面的两个类界说。

struct C180
{
 C180() {
  foo();
  this->foo();
 }
 virtual foo() {
  cout << "<< C180.foo this: " << this << " vtadr: " << *(void**)this << endl;
 }
};
struct C190 : public C180
{
 C190() {}
 virtual foo() {
  cout << "<< C190.foo this: " << this << " vtadr: " << *(void**)this << endl;
 }
};

父类中有一个虚函数,而且父类在它的结构函数中挪用了这个虚函数,挪用时它回收了两种要领一种是直接挪用,一种是通过this指针挪用。同时子类又重写了这个虚函数。

我们可以来预测一下假如结构一个C190的工具会产生什么环境。

我们知道,在结构一个工具时,首先会按工具的巨细获得一块内存(在heap上或在stack上),然后会把指向这块内存的指针做为this指针来挪用类的结构函数,对这块内存举办初始化。假如工具有父类就会先挪用父类的结构函数(并依次递归),假如有多个父类(多重担任)会依次对父类的结构函数举办挪用,并会适当的调解this指针的位置。在挪用完所有的父类的结构函数后,再执行本身的代码。

照上面的阐明结构C190时也会挪用C180的结构函数,这时在C180结构函数中的第一个foo挪用为静态绑定,会挪用到C180::foo()函数。第二个foo挪用是通过指针挪用的,这时多态行为会产生,应该挪用的是C190::foo()函数。

执行如下代码:

C190 obj;
obj.foo();

功效为:

<< C180.foo this: 0012F7A4 vtadr: 0045C404
<< C180.foo this: 0012F7A4 vtadr: 0045C404
<< C190.foo this: 0012F7A4 vtadr: 0045C400


#p#副标题#e#

和我们的阐明截然不同。前2行是结构C190时的输出,后1行是我们用静态绑定方法挪用的C190::foo()函数。第2行的输出说明多态行为并没有象预期的那样产生。并且较量输出的最后一列,发此刻挪用C180的结构函数时工具对应的虚表和结构后工具对应的虚表不是同一个。其实这正是机密的地址。

为此我查了一下C++尺度类型。在12.7.3条中有明晰的划定。这是一种特例,在这种环境下,即在结构子类时挪用父类的结构函数,而父类的结构函数中又挪用了虚成员函数,这个虚成员函数纵然被子类重写,也不答允产生多态的行为。即,这时必需要挪用父类的虚函数,而不子类重写后的虚函数。

我想这样做的原因是因为在挪用父类的结构函数时,工具中属于子类部门的成员变量是必定还没有初始化的,因为子类结构函数中的代码还没有被执行。假如这时答允多态的行为,即通过父类的结构函数挪用到了子类的虚函数,而这个虚函数要会见属于子类的数据成员时就有大概堕落。

我们看看VC7.1生成的汇编代码就可以很容易的领略这个行为了。

这是C190的结构函数:

01 00426FE0 push ebp
02 00426FE1 mov ebp,esp
03 00426FE3 sub esp,0CCh
04 00426FE9 push ebx
05 00426FEA push esi
06 00426FEB push edi
07 00426FEC push ecx
08 00426FED lea edi,[ebp+FFFFFF34h] 09 00426FF3 mov ecx,33h
10 00426FF8 mov eax,0CCCCCCCCh
11 00426FFD rep stos dword ptr [edi] 12 00426FFF pop ecx
13 00427000 mov dword ptr [ebp-8],ecx
14 00427003 mov ecx,dword ptr [ebp-8] 15 00427006 call 0041D451
16 0042700B mov eax,dword ptr [ebp-8] 17 0042700E mov dword ptr [eax],45C400h
18 00427014 mov eax,dword ptr [ebp-8] 19 00427017 pop edi
20 00427018 pop esi
21 00427019 pop ebx
22 0042701A add esp,0CCh
23 00427020 cmp ebp,esp
24 00427022 call 0041DDF2
25 00427027 mov esp,ebp
26 00427029 pop ebp
27 0042702A ret

开始部门的指令在前面几篇中连续表明过,这里不再详述。我们看看第15是对父类的结构函数C180::C180()的挪用,按照前文的说明,我们知道此时ecx中放的是this指针,也就是C190工具的地点。这时假如跳到this指针批向的地点看看会发明值为0xcccccccc即没有初始化,虚表指针也没有被初始化。那么我们随着跳到C180的结构函数看看。

01 00427040 push ebp
02 00427041 mov ebp,esp
03 00427043 sub esp,0CCh
04 00427049 push ebx
05 0042704A push esi
06 0042704B push edi
07 0042704C push ecx
08 0042704D lea edi,[ebp+FFFFFF34h] 09 00427053 mov ecx,33h
10 00427058 mov eax,0CCCCCCCCh
11 0042705D rep stos dword ptr [edi] 12 0042705F pop ecx
13 00427060 mov dword ptr [ebp-8],ecx
14 00427063 mov eax,dword ptr [ebp-8] 15 00427066 mov dword ptr [eax],45C404h
16 0042706C mov ecx,dword ptr [ebp-8] 17 0042706F call 0041DA8C
18 00427074 mov ecx,dword ptr [ebp-8] 19 00427077 call 0041DA8C
20 0042707C mov eax,dword ptr [ebp-8] 21 0042707F pop edi
22 00427080 pop esi
23 00427081 pop ebx
24 00427082 add esp,0CCh
25 00427088 cmp ebp,esp
26 0042708A call 0041DDF2
27 0042708F mov esp,ebp
28 00427091 pop ebp
29 00427092 ret

#p#副标题#e#

#p#分页标题#e#

看看第15行,在this指针的位置也就是工具的起始处,填入了一个4字节的值0x0045C404,其实这就是我们前面的打印过的C180的虚表地点。第16、17行和18、19行别离挪用了两次foo()函数,用的都是静态绑定。这个就有点奇怪,因为对后一个挪用我们利用了this指针,照理应该是动态绑定才对。可这里却是静态绑定,为什么编译器要做这个优化?我们担任往后看。

这个函数执行完后,我们再回到C190结构函数中,我们接着看C190结构函数汇编代码的第17行,这里又在工具的起始处从头填入了0x0045C400,包围了本来的值,而这个值就是我们前面打印过的真正的C190的虚表地点。

也就是说VC7.1是通过在挪用结构函数的真正代码前把工具的虚指针值配置为指向对应类的虚表来实现C++类型的相应语义。C++尺度中只划定了行为,并不划定详细编译器在实现这一行为时所用的要领。象我们上面看到的,纵然是通过this指针挪用,编译器也把它优化为静态绑定,也就是说纵然不做这个虚指针的调解也不会有错。之所以要调解我想大概是防备在被挪用的虚成员中又通过this指针来挪用其他的虚函数,不外谁会这么失常呢?

尚有值得一提的是,VC7.1中有一个扩展属性可以用来抑制编译器发生对虚指针举办调解的代码。我们可以在C180类的声明中插手这个属性。

struct __declspec(novtable) C180
{
 C180() {
  foo();
  this->foo();
 }
 virtual foo() {
  cout << "<< C180.foo this: " << this << " vtadr: " << *(void**)this << endl;
 }
};

这样再执行前面的代码,输出就会酿成:

<< C180.foo this: 0012F7A4 vtadr: CCCCCCCC
<< C180.foo this: 0012F7A4 vtadr: CCCCCCCC
<< C190.foo this: 0012F7A4 vtadr: 0045C400

由于编译器抑制了对虚指针的调解所以在调C180的结构函数时虚指针的值没有初始化,这时我们才看到多亏编译器把第二个通过this指针对foo的挪用优化成了静态绑定,不然由于虚指针没有初始化必然会呈现一个指针异常的错误,这就答复我们上面的谁人问题。

在这种环境下发生的汇编代码我就不列了,有乐趣的伴侣可以本身去看一看。别的对付析构函数的挪用,也请有乐趣的伴侣自行阐明一下。

别的这个属性在ATL的代码中大量的利用。在ATL中接口一般为纯虚基类,假如不消这个优化属性,由于在子类即实现类的结构函数中要挪用父类的结构函数,而编译器发生的父类结构函数又要配置虚指针的值。所以编译器必需要把父类的虚表构建出来。而实际上这个虚表是没有任何意义的,因为ATL的纯虚接口类的虚函数都是无实现的。这样不只仅是多了几行无用的设值指令,同时也挥霍了空间。有乐趣的伴侣可以自行验证一下。

 

    关键字:

天才代写-代写联系方式