您的位置:学习中国 推荐教程 C++C语言 正文
原作者:kuku 添加时间:2007-06-02 原文发表:2007-06-02 人气:9 来源:互联网

本文章共4956字,分4页,当前第2页,快速翻页:
 



  再观察一下第9行和第12行,可以看到计算出来的地址值是不一样的。这是因为第9行为给祖父类的成员变量赋值,而祖父类中有虚表指针存在,所以在得到对象的起始地址后,编译器给它加了4字节的偏移量以跳过虚指针。实际的得到地址的运算为: [ebp+ecx+FFFFF7F0h+4h],编译器在生成代码时会直接把最后一步运算做掉。


  我们再看一个例子,这个例子的继承结构和上一篇中是一样的,也是菱形结构。不同的是,每一个类都重写了顶层类声明的 虚函数 。代码如下: struct C041
{
 C041() : c_(0x01) {}
 virtual void foo() { c_ = 0x02; }
 char c_;
};
struct C140 : public virtual C041
{
 C140() : c_(0x02) {}
 virtual void foo() { c_ = 0x11; }
 char c_;
};
struct C141 : public virtual C041
{
 C141() : c_(0x03) {}
 virtual void foo() { c_ = 0x12; }
 char c_;
};
struct C150 : public C140, public C141
{
 C150() : c_(0x04) {}
 virtual void foo() { c_ = 0x21; }
 char c_;
};
  首先我们运行下面的代码,看看它们的内存布局。 PRINT_SIZE_DETAIL(C041)
PRINT_SIZE_DETAIL(C140)
PRINT_SIZE_DETAIL(C141)
PRINT_SIZE_DETAIL(C150)
  结果为: The size of C041 is 5
The detail of C041 is f0 c2 45 00 01
The size of C140 is 14
The detail of C140 is 48 c3 45 00 02 00 00 00 00 44 c3 45 00 01
The size of C141 is 14
The detail of C141 is 58 c3 45 00 03 00 00 00 00 54 c3 45 00 01
The size of C150 is 20
The detail of C150 is 74 c3 45 00 02 68 c3 45 00 03 04 00 00 00 00 64 c3 45 00 01
  和前面的布局不同之处在于,共享部分和前面的非共享部分之间多了4字节的0值。只有共享部分有虚表指针,这是因为派生类都没有定义自己的虚函数,只是重写了顶层类的虚函数。我们分析一下C150的对象布局。 |C140,5 |C141,5 |C150,1 |zero,4 |C041,5 |
|ospt,4,15 |m,1 |ospt,4,10 |m,1 |m,1 |4 |vtpt,4 |m1 |
  (注:为了不折行,我用了缩写。ospt代表偏移值指针、m代表成员变量、vtpt代表虚表指针。第一个数字是该区域的大小,即字节数。只有偏移值指针有第二个数字,第二个数字就是偏移值指针指向的偏移值的大小。)

  再看函数的调用: C150 obj;
PRINT_OBJ_ADR(obj)
obj.foo();
  输出的对象地址为: obj's address is : 0012F624
  最后一行函数调用的代码对应的汇编代码为: 00423F74 lea ecx,[ebp+FFFFF757h]
00423F7A call 0041DCA3
  单步执行后,我们可以看到ecx中的值为:0x0012F633,这个地址也就是obj对象布局中的祖父类部分的起始地址。通过上面的布局分析我们知道 C150起始的偏移值指针指向的值为15,即对象起始到共享部分(祖父类部分)的偏移值。上面输出的obj起始地址为0x0012F624加上十进制的 15后,正好是我们看到的ecx中的值0x0012f633。

 


  由于函数调用是作用于对象上,我们看到第二行的call指令是直接到地址的。

  在这里令人困惑的问题是,我们知道ecx是用来传递this指针的。在前一篇中,我们分析了在C110对象上的foo方法调用。在那个例子中,由于 foo是顶层类中定义的虚函数,并且没有被下面的派生类重写,因此通过子类对象调用这个方法时,编译器产生的代码是通过子类起始的偏移指针指向的偏移值来计算出祖父类部分的起始地址,并将这个地址做为this指针所指向的地址。但是在C150类中,foo不再是从祖父类继承的,而是被子类自己所重写。照理这时的this指针应该指向子类的起始地址,也就是0x0012F62E,而不是ecx中的值0x0012F633。

  我们跟进去看看C150::foo()的汇编代码,看它是怎样通过指向祖父类部分的this指针,来定位到子类的成员变量。
 

本文章更多内容<<上一页 - 1 - 2 - 3 - 4 - 下一页>>
本页地址
相关文章

C++之父Bjarne谈C++中的STL模板
C++箴言:用成员函数模板接受兼容类型
探索C++的秘密之详解extern C
C++箴言:为类型信息使用特征类
创建可移植的64位应用程序代码
设计OutLook风格的工具栏
C++程序员必需的修养
如何编写异常安全的C++代码
在c++程序中重启自己的一种方法
踏入C++中的雷区——C++内存管理详解
如何访问模板化基类中的名字
C++对象布局及多态之虚成员函数调用
理解隐式接口和编译期多态
C++程序中导出Word文档简易方法
C++程序设计从零开始之语句
对象布局及多态实现之成员函数的调用
C++箴言:谨慎使用私有继承
C++编程人员容易犯的10个C#错误
C语言 编程实例

相关评论


本文章所属分类:首页 推荐教程 C++C语言   C++C语言