|
蓝森林 http://www.lslnet.com 2006年6月6日 10:18
C++探索(virtual机制)(一部分)
有以下一个单一继承体系:
[code]class point2d {
public:
point2d(float x = 0.0, float y = 0.0)
: _x(x), _y(y) {};
virtual ~point2d() {};
virtual float x() { return _x; };
virtual float y() { return _y; };
virtual float z() { return 0.0; };
private:
float _x, _y;
};
class point3d : public point2d {
public:
point3d(float x = 0.0, float y = 0.0, float z = 0.0)
: point2d(x, y), _z(z) {};
~point3d() {};
float z() { return _z; };
private:
float _z;
};[/code]
(一) 单一继承下的Object内存布局
(1)point2d Object
class point2d 有两个float data member 共8个bytes,为了支持virtual机制,还需要额别安插一个data member,就是vptr(virtual table pointer)。
所以,整个point2d Object应该有12个bytes。
这些data member排列次序应该是:按data member的声明次序进行排列,而被编译额别生成的vptr应该被置于最后,不过,C++ standard对vptr的位置并没有规定,一般来说,都被置于最后。(好像VC++早前版本被置于最前)。
(2)point3d Object
class point3d继承自point2d,因此,在point3d Object中除了包含整个point2d subobject的12个bytes之外,还有它自己的data member。所以,point3d Object 共有16个bytes。
(二)virtual table
(1) virtual member function
为了支持执行期多态,virtual member function 被引入C++中,class point2d
含有三个virtual member function以支持多态。
有如下代码:
[code]point2d* p2d = new point3d(1.1, 2.2, 3.3);[/code]
那么:[code]p2d->;z(); [/code]是如何在执行期被确定z()的实体?
在point2d Object中被安插一个data member:vptr,它指向一个virtual table,在virtual table的每一个slot中安插了virtual function的实体位置。一般来说:virtual table的0偏移量是_type_info_class以支持RTTI(runtime type identification),而真正virtual function指针是从1开始计数。
所以,上例中,在vtbl(point3d中的point2d subobject的vritual table)中,是这样排列的:
[code]0:type_info_for_point3d
1: point3d::~point3d() à 指向point3d的destructor
2: point2d::x() 指向x()实体
3: point2d::y() 指向y()实体
4: point3d::z() 指向z()实体(注意:此时指向point3d::z())
形成一个表格形式。[/code]p2d->;z()被编译器转化为以下形式:
z(p2d);
进而,转化为:
(*p2d->;vptr[4])(p2d);
经过这种模型,p2d能很好地执行正确的z()实体。
(1) 如何确定实体
[code]point2d *ptr = new point3d(1.1, 2.2, 3.3);
ptr->;z();[/code]
以base class指针ptr如何寻找出derived class正确的z()实体?
观察并分析由gcc 3.3.1编译出的代码(这是一段main()函数码):
[code]movl $16, (%esp)
call _Znwj[/code]
16是什么?这就是point3d Object的字节数,
说明point3d object 将点16 bytes。
这是什么? 这是C++操作符new 。分配空间
其实在C++的new的背后,是通过函数_new()的调用实现的。
而函数的内部实现,是通过熟悉的malloc()函数。
那么这一段小段当然就是产生point3d Object了。
[code]movl %eax, %ebx // this 指针。
movl $0x40533333, %eax // 浮点数(3.3)
movl %eax, 12(%esp) // 入栈
movl $0x400ccccd, %eax // 浮点数(2.2)
movl %eax, 8(%esp)
movl $0x3f8ccccd, %eax // 浮点数(1.1)
movl %eax, 4(%esp)
movl %ebx, (%esp) // 压入this 指针
call _ZN7point3dC1Efff // call point3d 的constructor 进行初始化[/code]
到此,Object如何生产有个大致的了解了吧。
[code]movl %ebx, %eax
movl %eax, -8(%ebp) // -8(%ebp) = ptr指针[/code]
将上几段代码转化C伪代码如下:
[code]point3d *this = malloc(sizeof(point3d));
point3d(this, 1.1, 2.2, 3.3); // constructor()
point2d *ptr = (point2d *)this;[/code]
[code]movl -8(%ebp), %eax
movl (%eax), %edx // 提领ptr指针
addl $8, %edx // 找到 virtual table 中z()实体的索引位置
movl -8(%ebp), %eax
movl %eax, (%esp) // 压入ptr指针(this)
movl (%edx), %eax
call *%eax // call virtual function z()实体[/code]
我对以上代码有点疑问:
1、一般来说:vptr(virtual table pointer)是被置于object的最后。
但是从以上的代码来看,gcc 3.3.1将vptr置于obejct内存中
的最前面。这个特点从代码:movl (%eax), %edx可以看出。
其它的C++编译器,可能会是类似这样:
[code] movl –8(%ebp), %eax,
addl $8, %eax =>; 先找到vptr(未尾)
movl (%eax), %edx =>; 然后再提领 *vptr[/code] 2、 我对gcc3.3.1的virtual function table也有疑问,按计算
z()实体索引也不应该是2,即:2 * 4 个bytes。
一般来说:
[code] type_info_for_class 在索引0
point3d::~point3d()在索引1
point2d::x() 在索引2
point2d::y() 在索引3
point3d::z() 在索引4[/code]
以上的virtual function table布局,有可能安插了thunk技术
利用thunk码跳转到z()实体。
要解开以上两个疑问,要从constructor 和virtual function table中找答案。
(2) 下面是point3d的constructor
[code]movl 16(%ebp), %eax // 第3个参数(y = 2.2)
movl %eax, 8(%esp)
movl 12(%ebp), %eax // 第2个参数(x = 1.1)
movl %eax, 4(%esp)
movl 8(%ebp), %eax // 第1个参数(this 指针)
movl %eax, (%esp)
call _ZN7point2dC2Eff // call point2d 的constructor[/code]
// 也就是 point2d(this, x, y);
point2d subobject的起始地址与point3d object的起始地址是一样的。
对比一下point3d class constructor的定义:
point3d(float x = 0.0, float y = 0.0, float z = 0.0)
: _point2d(x, y) {}
先调用base class constructor。
[code]movl 8(%ebp), %eax // this
movl $_ZTV7point3d+8, (%eax) //设置vptr指针,被置于vtbl地址 [/code]
我推断这个$_ZTV7point3d+8 不是vtbl地址,就是vtbl+offset
而%eax存放point2d subobject地址,由此可见,vptr确实被置于前面
故直接提领ptr指针(this指针)就是vtbl地址,或是vtbl加上z()的偏移量。
但从词面上来看,应该是vtbl加上z()的偏移量。
$_ZTV7point3d应该才是真正是vritual function table地址。
$_ZTV7point3d+8应该是x()实体索引,那么前一段代码:
[color=red]addl $8, %edx 是真正索引到z()实体,8+8=4*4=16bytes。[/color]
证明了前面分析的z()应该在virtual function table的索引为4是正确的。
而前述virtual function 在virtual function table的布局也是正确的。
[code]movl 8(%ebp), %edx // this
movl 20(%ebp), %eax // 第4个参数(z = 3.3)
movl %eax, 12(%edx) // *(this + 3) = 3.3[/code]
以上代码,在调用base的constructor,再设置point3d object自已的 data member 即:_z。
*(this+3)= 3.3 即:_z = 3.3
对point3d的constructor工作进行一下整理,如下:
point3d(x, y, z): point2d(x, y), _z(z) {}; 被转化为:
[code] point3d(this, x, y, z) {
point2d::point2d(this, x, y);
_vptr = _vtbl;
_z = z;
};[/code]
(3) 看看point3d的virtual function table布局
[code]_ZTV7point3d:
.long 0
.long _ZTI7point3d
.long _ZN7point2d1xEv
.long _ZN7point2d1yEv
.long _ZN7point3d1zEv
.section .gnu.linkonce.t._ZN7point2dC2Eff,"ax",@progbits[/code]
以上是point3d的vtbl,可以明显看出,virtual function 的排列次序。
唯一和我讲述的不同是,0索引并不是type_info_for_class(RTTI)。我推断在多重继承和虚拟继承才有,因为,它们在需要的时候都需要调整this指针,从而产生执行期判断class type信息。而单一继承下是不城调整this指针的。一切都很自然地发生执行期多态。 |
C++探索(virtual机制)(一部分)
(4) 看看point2d 的constructor
[code]_ZN7point2dC2Eff:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax // 第一个参数(this指针)
movl $_ZTV7point2d+8, (%eax) // *this = _vptr_for_point2d
movl 8(%ebp), %edx // 第一个参数this
movl 12(%ebp), %eax // 第二个参数 (x = 1.1)
movl %eax, 4(%edx) // *(this+1) = 1.1
movl 8(%ebp), %edx
movl 16(%ebp), %eax // 第三个参数 (y = 2.2)
movl %eax, 8(%edx) // *(this+2) = 2.2;
popl %ebp[/code]
由上面可以看出,point2d的subobject内存布局为:
this: _vptr (point2d virtual table + 8 )
this + 4bytes: float x (data member x)
this + 8bytes: float y (data member y)
共12个bytes。
进一步证明gcc3.3.1的vptr指针是被置地最前面的。:) |
C++探索(virtual机制)(一部分)
补充一点:
(1)上述代码将产生两个virtual table, 一个对应point2d class, 一个对应point3d class。
以便于: point2d *p2d = new point2d; //设置point2d vptr
point3d *p3d = new point3d; //设置point3d vptr
(2) 在point3d 的constructor中,先调用point2d 的 constructor来设置point2d 的 vptr,令其指向 _vtbl_point2d。
再执行point3d 本身的constructor代码,设置point3d 的 vptr,令其指向_vtbl_point3d,从而覆盖了 point2d 的 vptr。因为 point2d 的 subobject 与 point3d object 的起始地址是一致的。 |
C++探索(virtual机制)(一部分)
哈哈~~
支持~~ |
C++探索(virtual机制)(一部分)
深奥。。小弟似懂非懂。。 |
| |