懂虚函数机制的来

lghe001 2016-11-14 07:21:31

class Base {
public:
Base(){cout <<"Base::Base" << endl; }
virtual void f() { cout <<"Base::f" << endl; }
virtual ~Base(){ cout <<"Base::~Base" << endl;}
int a;
};

class Drive:public Base
{
public:
Drive(){cout <<"Drive::Drive" << endl; }
virtual void f() { cout <<"Drive::f" << endl; }
~Drive(){ cout <<"Drive::~Drive" << endl;}
};

typedef void (*Fun)(void);
typedef void (Base::*Fun2)(void);
typedef void (Drive::*Fun3)(void);

int fun(Base b)
{
b.f();
return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
Drive d;
d.a = 5;

fun(d);

Base* pbase = &((Base)d);

printf("d:%p\t pbase:%p\n",&d, pbase);
printf("d.a:%p\t pbase->a:%p\n",d.a, pbase->a);
printf("&d.a:%p\t &pbase->a:%p\n",&d.a, &pbase->a);

pbase->f();

Fun2 pFun2 = &Base::f;
(pbase->*pFun2)();
printf("&Base::f:%p\n",pFun2);

Fun pFun = (Fun)*((int*)*(int*)(pbase));
pFun();
printf("虚表第一项地址内容:%p\n\n",pFun);

pbase = &(d);
printf("d:%p\t pbase:%p\n",&d, pbase);
printf("d.a:%p\t pbase->a:%p\n",d.a, pbase->a);
printf("&d.a:%p\t &pbase->a:%p\n",&d.a, &pbase->a);

pbase->f();

Fun3 pFun3 = &Drive::f;
((Drive *)pbase->*pFun3)();
printf("&Drive::f:%p\n",pFun3);


pFun = (Fun)*((int*)*(int*)(pbase));
pFun();
printf("虚表第一项地址内容:%p\n\n",pFun);

return 0;
}

vs2008平台运行结果:
Base::Base
Drive::Drive //d的构造过程

Base::f
Base::~Base //离开函数fun作用域,形参对象析构

Base::~Base //Base* pbase = &((Base)d);

d:0048F9F8 pbase:0048EFA0
d.a:00000005 pbase->a:00000005
&d.a:0048F9FC &pbase->a:0048EFA4

Base::f //pbase->f(),没问题
Base::f //(pbase->*pFun2)();通过成员函数指针调用
&Base::f:002A150A
Base::f //pFun();
虚表第一项地址内容:002A1389 //为什么不是002A150A?但是实际都是调用Base的f,Base的成员函数f难道有两份实现?

d:0048F9F8 pbase:0048F9F8
d.a:00000005 pbase->a:00000005
&d.a:0048F9FC &pbase->a:0048F9FC
Drive::f
Drive::f
&Drive::f:002A150A
Drive::f
虚表第一项地址内容:002A151E

疑惑如下:
1,fun(d);函数是传值方式传参数,那么应该生成一个Base的临时对象并用d来初始化,那么应该打印Base类的构造函数呀,但是实际却没有打印
2,Base* pbase = &((Base)d); 这里不是把d截断么?printf("d:%p\t pbase:%p\n",&d, pbase)打印的是:d:0048F9F8 pbase:0048EFA0,可以看得出是另外生成了Base对象而不是截断,为什么实际是打印了base的析构函数,而且只有析构没有构造!如果是在另外一块内存生成一个Base对象,并用d里面的Base部分来初始化它,为什么没有调Base的构造函数,而且析构函数来得也奇怪。
3,通过成员函数指针拿到的Base的f函数的地址pFun2和通过虚表拿到的Base的f函数的地址pFun为什么不一样?按道理编译器不可能对Base的成员函数f生成两份实现的呀
...全文
509 16 打赏 收藏 转发到动态 举报
写回复
用AI写文章
16 条回复
切换为时间正序
请发表友善的回复…
发表回复
FD_2013 2016-11-16
  • 打赏
  • 举报
回复
虚函数: 1.一个实现另一个没实现,调实现的那个。 2.两个都实现了,按继承顺序调。
sdghchj 2016-11-15
  • 打赏
  • 举报
回复
更准确点说,强制转换是先到转换类型的所有构造函数中去找,如果找不到对应的,再到被转换类型的所有operator里去找对应的。 3.(Fun)*((int*)*(int*)(pbase));这种方式是Derive::f的真实地址;而&Derive::f得到的不是真实的函数地址,因为虚函数在编译时是不知道具体地址的,只能在运行时才能确定,所以&Derive::f返回的一般是基于虚表起始地址的偏移量(g++就是),不同编译器返回的东西可能不同,msvc返回的可能(猜测)是虚表起始地址(也即是对象起始地址)+偏移量
Saleayas 2016-11-15
  • 打赏
  • 举报
回复
虚函数其实就是可以从基类引用呼叫的,但是在子类中实现的函数。 此时,在函数调用的时候,传入的是基类的 this 指针 ThisB。 但是在函数实现的时候,函数的 this 指针却是子类的 ThisD。 这两个指针可能一致,也可能不一致,比如多重继承。 此时需要进行 this 指针转换。 所以当虚函数被继承重写之后,需要一个类似于代理的函数 B,这个函数很简单, 就是调整 this 的偏移量,然后呼叫真正的重写函数 A。 所以,虚表里面的函数地址,其实是 B 函数,而 B 函数会呼叫 A 函数。 如果直接以 C++ 的类实例呼叫,此时会直接呼叫 A。 如果以实例的引用呼叫,此时就进入多态模式,呼叫虚函数 B,B函数调整 this,呼叫 A。
lghe001 2016-11-15
  • 打赏
  • 举报
回复
我以为强制转换会像基本类型的那样,只是对原来的内存按强制转换后的类型来重新解释而已.OK,1,2算是搞明白了
sdghchj 2016-11-15
  • 打赏
  • 举报
回复
上面说错,强制转换是先找Base类的拷贝构造函数,如果找不到对应的,再去找Derive的operator Base()
sdghchj 2016-11-15
  • 打赏
  • 举报
回复
1.调用的是编译器自动生成的拷贝构造函数,不是你写的构造函数。 2.Base* pbase = &((Base)d);强制转换是调用编译器给Derive自动生成的operator Base()即Base操作符 (而不是调用Base的构造函数),生成临时对象,对临时对象右值取地址是不符合标准的(只是默认情况下VS不会报错,而g++会报错),临时对象马上析构,后面的pbase的所有使用都是未定义行为,即使调用了它的f()因没涉及到成员变量所以没有发生崩溃。
fefe82 2016-11-15
  • 打赏
  • 举报
回复
引用 5 楼 lghe001 的回复:
[quote=引用 2 楼 fefe82 的回复:] 1.2. 用的拷贝构造函数。没有写的话编译器会自动生成一个默认的。
印象中,子类的新添加的成员变量内存布局时只是加在父类的后面,所以把子类对象强制转为父类对象,应该是对子类对象进行了截断而已,也就是重新按父类类型来解释原来子类对象的内存不是么?[/quote] “新添加的成员变量内存布局时只是加在父类的后面” 这个有编译器决定。 “子类对象强制转为父类对象” 这个会生成一个新对象。(使用拷贝构造函数,拷贝构造函数的参数是你所说的“截断”的对象) 强制转换基本都会生成一个新的(临时)对象。 “父类类型来解释原来子类对象的内存” 这个也与编译器、继承关系(是否有虚函数,是否单继承,是否虚继承 .....)都有关系。不过最终还是编译器说了算。 =============== 子类的指针转换为父类的指针,子类的对象绑定到父类的引用,通常不生成新对象。 父类的应用绑定到子类的对象时,是直接绑定到父类子对象的。
lghe001 2016-11-15
  • 打赏
  • 举报
回复
引用 4 楼 Saleayas 的回复:
C++ 的虚函数的实现由两个函数。 一个是虚表的实现函数A,一个是 C++ 类成员的实现函数B。 B 在对 this 指针转换之后 再呼叫 A。 比如 class C { virtual void F(); }; C c; c.F(); 呼叫的是 B。 (&c)->F(); 呼叫的是 A;
意思是B其实并不是真正的F()代码实现的地址?B里面通过调用A才跳转到F()的代码实现?
lghe001 2016-11-15
  • 打赏
  • 举报
回复
引用 2 楼 fefe82 的回复:
1.2. 用的拷贝构造函数。没有写的话编译器会自动生成一个默认的。
印象中,子类的新添加的成员变量内存布局时只是加在父类的后面,所以把子类对象强制转为父类对象,应该是对子类对象进行了截断而已,也就是重新按父类类型来解释原来子类对象的内存不是么?
ArthurJava 2016-11-15
  • 打赏
  • 举报
回复
把构造函数写全就明白,不是一个对象
fefe82 2016-11-15
  • 打赏
  • 举报
回复
引用 9 楼 sdghchj 的回复:
上面说错,强制转换是先找Base类的拷贝构造函数,如果找不到对应的,再去找Derive的operator Base()
这个时候只会使用构造函数。 operator Base() 最多被用来提供 copy constructor 的参数。如果 copy constructor 不存在(系统默认生成的也不存在,或无法调用(比如 private)),只有 operator Base() 是没有用的。
赵4老师 2016-11-15
  • 打赏
  • 举报
回复
《深度探索C++对象模型》 《C++反汇编与逆向分析技术揭秘》
Saleayas 2016-11-14
  • 打赏
  • 举报
回复
C++ 的虚函数的实现由两个函数。 一个是虚表的实现函数A,一个是 C++ 类成员的实现函数B。 B 在对 this 指针转换之后 再呼叫 A。 比如 class C { virtual void F(); }; C c; c.F(); 呼叫的是 B。 (&c)->F(); 呼叫的是 A;
ID870177103 2016-11-14
  • 打赏
  • 举报
回复
2楼正解 3.虚成员函数的地址得到的其实是虚表的偏移(某种格式),不是真正的函数地址
fefe82 2016-11-14
  • 打赏
  • 举报
回复
1.2. 用的拷贝构造函数。没有写的话编译器会自动生成一个默认的。
yshuise 2016-11-14
  • 打赏
  • 举报
回复
class Base { public: Base(){cout <<"Base::Base" << endl; } virtual void f() { cout <<"Base::f" << endl; } virtual ~Base(){ cout <<"Base::~Base" << endl;} int a; }; ==================== 最好纯虚类

64,676

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧