粗略阅读了《深度探索c++对象模型》一书后,对c++对象底层的内存布局有了一些了解,但同时,也产生了一些疑惑:
1、将子类指针用dynamic_cast转成父类指针之后,其虚表指针会相应变化么?
2、父类转子类呢?
以下是验证疑惑的代码:
#include <iostream> class A { public: virtual void func() { std::cout << "a: " << a << std::endl; } int a = 1; }; class B : public A { public: virtual void func() { std::cout << "b: " << b << std::endl; } int b = 2; }; int main() { A* pa1 = new B(); B* pb1 = (B*)pa1; B* pb2 = new B(); A* pa2 = dynamic_cast<A*>(pb2); A* pa3 = new A(); B* pb3 = dynamic_cast<B*>(pa3); pa1->func(); pb1->func(); pa2->func(); pb2->func(); pa3->func(); pb3->func(); return 0; }
编译:g++ object.cpp -o main --std=c++11 -g
执行结果:
$ ./main b: 2 b: 2 b: 2 b: 2 a: 1 Segmentation fault
在pb3调用func的时候,发生了一个段错误。这个不大符合我的预期,查询了下dynamic_cast的用法,发现:
'dynamic_cast'只用于对象的指针和引用。当用于多态类型时,它允许任意的隐式类型转换以及相反过程。不过,与static_cast不同,在后一种情况里(注:即隐式转换的相反过程),dynamic_cast会检查操作是否有效。也就是说,它会检查转换是否会返回一个被请求的有效的完整对象。 检测在运行时进行。如果被转换的指针不是一个被请求的有效完整的对象指针,返回值为NULL.
如上所说,当dynamic_cast检测到转换不成功的时候,返回的是个NULL指针,因此就不难解释,为什么会发生段错误了。
另外,A* pa2是从B*转换过来的,但其实调用的还是类型B的方法,由此可以推断,类型的虚表指针在类型分配内存的时候就确定了,dynamic_cast的转换并不能将虚表指针重新赋值。
重新调整了下main函数代码:
1 int main() { 2 A* pa1 = new B(); 3 B* pb1 = (B*)pa1; 4 5 B* pb2 = new B(); 6 A* pa2 = dynamic_cast<A*>(pb2); 7 A* pa21 = (A*)pb2; 8 A* pa22 = static_cast<A*>(pb2); 9 10 A* pa3 = new A(); 11 B* pb3 = static_cast<B*>(pa3); 12 13 pa1->func(); 14 pb1->func(); 15 pb2->func(); 16 pa2->func(); 17 pa21->func(); 18 pa22->func(); 19 pa3->func(); 20 pb3->func(); 21 return 0; 22 }
运行结果如下:
$ ./main b: 2 b: 2 b: 2 b: 2 b: 2 b: 2 a: 1 a: 1
可见,不管是强转、static_cast还是dynamic_cast,都不会影响虚表指针,可以通过GDB来验证下:
(gdb) p *pa1 $1 = {_vptr.A = 0x400d10 <vtable for B+16>, a = 1} (gdb) p *pb1 $2 = {<A> = {_vptr.A = 0x400d10 <vtable for B+16>, a = 1}, b = 2} (gdb) p *pb2 $3 = {<A> = {_vptr.A = 0x400d10 <vtable for B+16>, a = 1}, b = 2} (gdb) p *pa2 $4 = {_vptr.A = 0x400d10 <vtable for B+16>, a = 1} (gdb) p *pa21 $5 = {_vptr.A = 0x400d10 <vtable for B+16>, a = 1} (gdb) p *pa22 $6 = {_vptr.A = 0x400d10 <vtable for B+16>, a = 1} (gdb) p *pa3 $7 = {_vptr.A = 0x400d30 <vtable for A+16>, a = 1} (gdb) p *pb3 $8 = {<A> = {_vptr.A = 0x400d30 <vtable for A+16>, a = 1}, b = 0}
可以看到用new B() 方式定义的对象,虚表指针都是指向 B类的虚表指针,用new A()方式定义的对象,虚表指针都指向了A。
由此可以看出,对象的虚表指针,在定义的时候,就确定了,并且不会随着强转、static_cast或dynamic_cast改变。