转:C++:从子类访问父类的私有函数
众所周知,c和c++的数组都是不安全的,因为无论c还是c++都不提供数组边界检查功能,这使得数组溢出成为可能。
从某个意义上说,c和c++是一种缺少监督的语言,然而这也正是其魅力所在。c++给予程序员更大的自由,相比于使用JAVA编程的束手束脚,c++程序员拥有了更大的权力,同时也拥有更多的机遇来玩弄一些技巧,比如说,从子类调用父类的私有函数。
从子类调用父类的private函数?我没听错么?
当然没有!
尽管从各种c++书籍中我们得到的信息是子类从父类继承的仅有protected成员和public成员,而父类的private成员无法被子类继承,也无法被子类访问,但是当父类的private函数是一个虚函数时,我们却可以通过读取VTABLE表中信息,从而找到父类虚函数的地址,进而调用它。
先回忆一下,c++的多态是怎样实现的。
当c++的类中出现virtual关键字时,该类就拥有了一张VTABLE表。VTABLE表的内容包括了各个虚函数在虚拟内存中的偏移量,也就是说如果类A拥有三个虚函数:f1,f2,f3,那么在类A的虚函数表VTABLE中将依次存放f1,f2,f3的偏移地址。
那么,当类A仅拥有一个虚函数时,类A实例所占内存大小,也就是sizeof(A)与类A拥有两个、三个,甚至更多个虚函数时的sizeof(A)有区别么?不,毫无区别。如果一个类的所有成员都是被virtual修饰的虚函数,那么当您使用sizeof(A)查看其大小时,结果无一例外的都是4——在32位系统中,四字节恰恰是一个整型数的大小,也恰恰是一个指针的大小。
这是为什么?
因为对于类A而言,它并不需要知道有多少个虚函数,它需要的仅仅是一个指向VTABLE的指针,这个指针通常被叫作vptr。它指向了VTABLE,至于究竟有多少个虚函数,只需在VTABLE中寻找就是。让我们想象一下,vptr就向一个路标,它指向一个名叫VTABLE的公司,至于公司里有多少员工,你必须进入VTABLE公司才会知道。
好了,现在我们总结一下:
结论一:在有虚函数的类中,一定有一个vptr指向VTABLE
结论二:VTABLE中依次存储了各个虚函数在虚拟内存中的偏移地址
现在,我们再来介绍另两个c++的规律。
规律一:在任何类中,vptr一定存储在该类实例的前四个字节中。
规律二:当子类和父类同时拥有虚函数时,子类的VTABLE中也同时会拥有父类和子类的虚函数偏移地址,而且子类的虚函数偏移地址一定是追加在父类虚函数偏移地址之后的。
也就是说,如果有如下两个类:
class A {
private:
virtual void WhoAmI() {
cout << "I am class A" << endl;
}
};
class B:public A {
public:
void WhoAmIForB() {
cout << "I am class B" << endl;
}
};
那么,实例
A a;
B b;
中各有一个vptr,其中a的vptr为(int*)(*(int*)(&a)),而b的vptr为(int*)(*(int*)(&b)),
这两个vptr又分别指向各自的VTABLE,其中父类A的VTABLE中存储的内容是:A::WhoAmI的偏移地址,而子类B的VTABLE呢?
子类B的VTABLE中依次存储了A::WhoAmI的偏移地址,B::WhoAmIForB的偏移地址。
注意了,关键就在这里:A的虚函数都是私有的,不是么?但是编译器链接器在此时却似乎将关键字private忘记了,无论这些虚函数是private还是public的,它们的偏移地址都毫无例外的存放在了子类的VTABLE中!
这就是破绽!你可以刺出至命的一剑了!
我们既然知道子类实例的vptr,为什么不能推算出子类的VTABLE?
既然知道子类的VTABLE,根据规定律二,为什么不能推算出父类的虚函数偏移量?
答案就是:父类的第一个虚函数所在偏移量是(int*)(*(子类的vptr)),也就是——(int*)(*(int*)(*(int*)(&b)))。
当我们强制将其转换为一个指向函数的指针时,就可以调用它,从而实现了从子类调用父类私有函数的行为。
试运行如下一段代码:
#include <iostream>
using namespace std;
class A {
private:
virtual void WhoAmI() {
cout << "I am class A" << endl;
}
virtual void f0() {
cout << "This is f0" << endl;
}
virtual void f1() {
cout << "This is f1" << endl;
}
};
class B:public A {
public:
void WhoAmIForB() {
cout << "I am class B" << endl;
}
};
typedef void (*FUNC)();
int main(int argc,char * argv[])
{
B b;
b.WhoAmIForB();
//b.WhoAmI(); error C2248: “A::WhoAmI”: 无法访问 private 成员(在“A”类中声明)
FUNC func = (FUNC)((int*)(*(int*)(*(int*)(&b))));
func();
return ;
}
转:C++:从子类访问父类的私有函数的更多相关文章
- Java子类访问父类的私有成员变量
/**子类会继承父类所有的属性和方法. * 但是根据不同的权限标识符,子类不可见父类的私有变量,但可以通过父类的公共方法访问私有变量 * 所以对于重名变量,子类和父类都各有一份. * 对于子类和父类中 ...
- javascript中uber实现子类访问父类成员
function Animal(){} Animal.prototype={ name:"animal", toString:function(){ console.log(thi ...
- C++ 学习笔记 (六) 继承- 子类与父类有同名函数,变量
学习了类的继承,今天说一下当父类与子类中有同名函数和变量时那么程序将怎么执行.首先明确当基类和子类有同名函数或者变量时,子类依然从父类继承. 举例说明: 例程说明: 父类和子类有同名的成员 data: ...
- C++ 子类继承父类纯虚函数、虚函数和普通函数的区别
C++三大特性:封装.继承.多态,今天给大家好好说说继承的奥妙 1.虚函数: C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现.子类可以重写父类的虚函数实现子类 ...
- spring使用注解通过子类注入父类的私有变量
方法一 通过 super.setBaseDao方法设置父类私有变量 父类 public class BaseServiceImpl { private BaseDao baseDao; publ ...
- 子类覆写的变量被private隐藏,强制转换方式通过子类访问父类的被覆写变量:
import static java.lang.System.*; public class SuperParent{ public static void main(String[] args){ ...
- 【C++】子类访问父类typedef的问题
class A { public: typedef int* pointer; }; class B :public A { public: pointer b; }; 这段代码运行没有问题,子类继承 ...
- OC 继承子类对象调用方法机制 子类对象访问父类中的实例变量
在继承中,子类对象如何调用到正确方法的机制 每一个Objective - C对象都有一个隐藏的指针指向类的代码,当向一个对象发送消息的时候,当前的对象会首先在当前类里去查找相应的方法,如果找到的话,直 ...
- Java vs C++:子类覆盖父类函数时缩小可访问性的不同设计
Java 和 C++ 都是面向对象的语言,允许对象之间的继承.两个语言的继承都设置有允许子类覆盖父类的“虚函数”,加引号是因为 Java 中没有虚函数这一术语,但是我们的确可以把 Java 的所有函数 ...
随机推荐
- 在SSIS包中使用 Checkpoint从失败处重新启动包
使用SSIS做ETL的过程中会遇到各种各样的错误,对于一些大数据量的Job失败以后我们不希望重新运行,因为重新运行的时间开销是非常大的,我们只希望从失败的部分开始运行,这样可以省去很多的时间. SSI ...
- linux 使用ssh到远端并且使用while的坑
如果要使用ssh批量登录到其它系统上操作时,我们会采用循环的方式去处理,那么这里存在一个巨大坑,你必须要小心了. 现在是想用一个脚本获取远程服务器端/root下面的文件: #!/bin/bash ca ...
- c语言中-----分配内存函数
原型: void * realloc(void *p, size_t size); realloc 可以对给定的指针所指的空间进行扩大 或者 缩小, 原有内存的数据保持不变.当然,对于缩小,则缩小部 ...
- iOS:原生二维码扫描
做iOS的二维码扫描,有两个第三方库可以选择,ZBar和ZXing.今天要介绍的是iOS7.0后AVFoundation框架提供的原生二维码扫描. 首先需要添加AVFoundation.framewo ...
- dubbo架构演变之路
背景 (#) 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. 单一应用架构 当网站流量很小时, ...
- beanUtils操作bean的属性
beanUtils操纵bean属性: 需要jar包commons-beanutils-x.x.x.jar 同时commons-beanutils-x.x.x.jar需要commons-loggi ...
- iphone手机上的click和touch
在iphone手机上绑定click事件时,当你触发点击事件时,你绑定的click事件的DOM节点,会自动被一块浮层选中.所以如果使用事件委托来做事件绑定会造成很差的用户体验. 使用touchstart ...
- Arduino 时钟模块(clock module) DS1306
http://www.pjrc.com/teensy/td_libs_DS1307RTC.html 下载相关的库程序 连接: DS1306: 1.接3.3V 2.SDA接A4 3.SCL接A5 读取: ...
- javascriptDOM编程艺术_学习笔记_知识点 动态创建标记
传统技术:document.write 和 innerHTML 深入剖析DOM方法:createElement.createTextNode.appendChild 和 insertBefore ...
- 关于$.ajax的请求格式
1 $.ajax({ 2 url: "http://localhost:5311/UserService.asmx/GetLoginId",//请求的URL 3 type: &qu ...