[TOC]
先上面试题,问打印结果是什么?
#include <stdio.h> class A{ public: void fun() {printf("A");} }; class B : public A{ public: virtual void fun(){printf("B");} }; class C : public B{ public: void fun() {printf("C");} }; class D : public C{ public: virtual void fun() {printf("D");} virtual void funD() {printf("D_Test");} }; class E : public D { public: virtual void fun(){printf("E");} virtual void funE(){printf("E_Test");} }; class E1 : public D{ public: virtual void fun(){printf("E1");} virtual void funE1(){printf("E1_Test");} }; int main(){ A* p1 = (A*)new B; p1->fun();printf(", "); B* p2 = (B*)new C; p2->fun(); printf(", "); B* p3 = (B*)new D; p3->fun(); printf(", "); B* p4 = (E*)new D; p4->fun(); printf(", "); D* p5 = (D*)new E; p5->funD(); printf(", "); E1* p6 = (E1*)new E; p6->funE1(); printf(", "); p6->funD(); printf(", "); printf("%d, %d/n", sizeof(B) - sizeof(A), sizeof(D) - sizeof(A)); }
再看到这里的时候,再做一遍吧,答案放在最后
简单的结构如下图,参考[2]描述地超级详细,也利于理解,可以仔细阅读一下。
C++对象模型
另外,为了解题,还要讲讲编译链接以及函数调用的简单原理
编译的时候,静态以及非静态成员函数也是已经编译好了的。虚函数,编译器能够确定的是偏移量,待运行的时候会去找具体对象的vptr,指向该对象所在类对应的虚表,找到真正应该调用的函数[3]。 文章[4]也说到了,虚函数和普通函数的在汇编一层的表现形式,虚函数要通过虚表去查找真正该调用的函数,而普通函数直接就找到了的。
现在,运用上面的结论来回答问题
看一下测试代码和结果如下:在上面面试题后面加入以下测试代码
printf("===========vritual address=========/n"); printf("vtable p1: %x/n", *((int*)p1)); printf("vtable p2: %x/n", *((int*)p2)); printf("vtable p3: %x/n", *((int*)p3)); printf("vtable p4: %x/n", *((int*)p4)); printf("vtable p5: %x/n", *((int*)p5)); printf("vtable p6: %x/n", *((int*)p6)); printf("p1 virtual fun: %x/n", *(int*)*((int*)p1)); printf("p2 virtual fun: %x/n", *(int*)*((int*)p2)); printf("p3 virtual fun: %x/n", *(int*)*((int*)p3)); printf("p4 virtual fun: %x/n", *(int*)*((int*)p4)); printf("p5 virtual fun 1: %x, offset: %x/n", *(int*)*((int*)p5), &E::fun); printf("p5 virtual fun 2: %x, offset: %x/n", *((int*)*((int*)p5)+1), &E::funD); printf("p5 virtual fun 3: %x, offset: %x/n", *((int*)*((int*)p5)+2), &E::funE); printf("p6 virtual fun 1: %x, offset: %x/n", *(int*)*((int*)p6), &E1::fun); printf("p6 virtual fun 2: %x, offset: %x/n", *((int*)*((int*)p6)+1), &E1::funD); printf("p6 virtual fun 3: %x, offset: %x/n", *((int*)*((int*)p6)+2), &E1::funE1);
结果:
===========vritual address========= vtable p1: 8049290 vtable p2: 8049258 vtable p3: 80492a0 vtable p4: 80492a0 vtable p5: 80492c0 vtable p6: 80492c0 p1 virtual fun: 8048e1e p2 virtual fun: 8048e0a p3 virtual fun: 8048e32 p4 virtual fun: 8048e32 p5 virtual fun 1: 8048e46, offset: 1 p5 virtual fun 2: 8048dce, offset: 5 p5 virtual fun 3: 8048de2, offset: 9 p6 virtual fun 1: 8048e46, offset: 1 p6 virtual fun 2: 8048dce, offset: 5 p6 virtual fun 3: 8048de2, offset: 9
[1] 图说C++对象模型:对象内存布局详解
[2] C++虚表,你搞懂了吗?
[3] C/C++杂记:虚函数的实现的基本原理
[4] 关于C++虚函数与普通函数的编译与调用机制
答案:A, C, D, D, D_test, E_test, D_test