C++虚函数表的理解、通过虚函数表访问非public成员函数

阅读了陈皓老师的虚函数表解析:  点击打开链接    ,以及虚函数表存放位置一文: 点击打开链接

对C++如何实现多态应该有了个粗浅认识.

1)虚函数表是一个数组,每个元素存储virtual函数的指针
2)如果一个类存在虚函数,编译器一般会将该类实例的前4/8个字节(取决于系统位数以及编译器)初始化为虚函数表的地址.
3)虚函数表是针对类而言的,类似于static成员变量,存放在全局数据区.
(c/c++所用内存分为五种:栈区,堆区,程序代码区,全局数据区/静态区,文字常量区.)
4)类型为基类的指针(引用)能够访问函数名称是由基类决定的,因为指针类型是编译期决定的,属于静态绑定。
5)类型为基类的指针(引用)是指向基类/派生类函数是由在分配在stack或heap的实际对象决定的,因为在stack/heap构造对象都是运行期行为,属于动态绑定。

有个Base类以及Derived类,故意将Base的func都写成private:

class Base{
private:
	virtual void func1(){
		cout<<"Base::func1()"<<endl;
	}
	virtual void func2(){
		cout<<"Base::func2()"<<endl;
	}
	virtual void func3(){
		cout<<"Base::func3()"<<endl;
	}

};

class Derived:public Base
{
public:
	void func2(){
		cout<<"Derived::func2()"<<endl;
	}
	virtual void func4(){
		cout<<"Derived::func4()"<<endl;
	}
	void func5(){
		cout<<"Derived::func5()"<<endl;
	}
};

根据上述的3),Base类和Derived类都有一份类独有的虚函数表vtbl,每个实例都有一个指针指向它们。
根据4),通过Base类的指针只能调用func1、func2、func3,尽管实际指向类型有可能是Derived类。程序运行时,若Base* ptr绑定的是Derived类对象;
根据5),则ptr通过Derived对象内的vtbl指针实现多态。
根据名称遮掩规则,通过ptr调用func1、func2、func3,结果应该分别是Base::func1、Derived::func2、Base::func3。

调用:

	Derived obj;
	Base* ptrb=&obj;
	//ptrb->func2();	//err
	//ptrb->func4();	//err

而在这个例子中,调用func2和func4出现了编译错误。原因其实上面已经分析过:

Base类里有func2,但是访问权限是private,访问权限在编译期就已经决定了,Derived重新声明为public不起作用;Base类里没有func4,同样在编译期就已经决定了。

了解了虚函数表,并根据1)、2),可通过一些手段绕开类的访问权限,调用非public成员函数(这也是C++不足之处):

	typedef void (*func)(void);
	Derived obj;

	cout<<"obj_addr "<<(&obj)<<endl;
	cout<<"vtbl_addr_addr "<<(long*)(&obj)<<endl;
	cout<<"vtbl_addr "<<"0x"<<hex<<*(long*)(&obj)<<endl;
	cout<<"fun1_addr_addr "<<(long *)(*(long*)(&obj))<<endl;	//output hexadecimal: cout << hex << num
	//cout<<"func1_addr "<<"0x"<<hex<<*(long *)(*(long*)(&obj))<<endl;
	func ptr1=(func)(*(long *)(*(long *)(&obj)));
	ptr1();
	func ptr2=(func)(*((long *)(*(long *)(&obj))+1));
	ptr2();
	func ptr3=(func)(*((long *)(*(long *)(&obj))+2));
	ptr3();
	func ptr4=(func)(*((long *)(*(long *)(&obj))+3));
	ptr4();
	//func ptr5=(func)(*((long *)(*(long *)(&obj))+4));	//seg fault, non-virutal isnt included in vtbl
	//ptr5();

这里&obj是对象的地址。(long*)(&obj)将对象的地址转为vtbl指针的地址。

vtbl指针的地址解引用后是vtbl指针。(long *)(*(long*)(&obj))再次将vtbl的指针转为vtbl内指向第一个元素的指针。

最后将指向第一个元素的指针转为函数指针,即可通过此函数指针调用。

结果:

这里已经访问到了Base的private func1、func3。此外注意,func5是个non-virtual,不在虚函数表内,如果调用程序可能奔溃。

那为什么Derived里重写func2的时候没有加virtual,也在虚函数表中呢?

c++规定当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在子类重新声明该虚函数时,可加可不加,但习惯上每一层声明函数时都加virtual,使程序更加清晰。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值