科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道应用软件C 对象布局及多态之虚成员函数调用

C 对象布局及多态之虚成员函数调用

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

在构造函数中调用虚成员函数,虽然这是个不很常用的技术,但研究一下可以加深对虚函数机制及对象构造过程的理解。这个问题也和一般直观上的认识有所差异。

作者:51CTO.com整理 来源:51CTO.com整理 2007年9月14日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
开始部分的指令在前面几篇中陆续解释过,这里不再详述。我们看看第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  

看看第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的纯虚接口类的虚函数都是无实现的。这样不仅仅是多了几行无用的设值指令,同时也浪费了空间。有兴趣的朋友可以自行验证一下。

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章