转: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 的所有函数 ...
随机推荐
- [置顶] fmt日期格式化
jstl中的日期格式化 <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> & ...
- Cocos2d-x lua游戏开发之安装Lua到mac系统
注意:mac ox .lua version :5.15 下载lua官网的lua, 注意:最好是5.15下面.5.2的lua不支持table的getn()方法,这让我情何以堪.(获取table长度.相 ...
- UIView 中 frame, bounds, center 属性的关系
最近一直在学 iOS 开发,所以专门创建了这样一个类别,将自己学习中的一些问题整理,记录下来.由于自己是初学者,所以所写的文章非常基础,写这个类别一是为了给自己留下存 档,二是为了给和我有同样问题的初 ...
- css3的一些属性
以前还没有注意过css的一些属性,近期发现有一些样式很好用,现在整理一遍. CSS3 动画属性 @keyframes : 规定动画 可以通过keyframes 改变一个块的样式当然这是要配合anima ...
- C#中linq报“Character literal must contain exactly one character”的错误提示
后台代码使用linq提示"Character literal must contain exactly one character": 网上看了一下提示在部分linq语句中直接写入 ...
- c# 迭代器 与 集合 IEnumerable.GetEnumerator 方法
示例来源 :msdn 参考:https://msdn.microsoft.com/zh-cn/library/dscyy5s0(v=vs.110).aspx 使用匿名方法.迭代器和部分选件类创建简洁的 ...
- HTML+CSS笔记 CSS入门
简介: </span>年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的<span>脚本解释程序</span>,作为ABC语言的一种继承. & ...
- Protel99se教程五:protel99se的自动布线
在上一节课的protel99se教程中,我们给大家演示的是,如何快速的将SCH原理PCB,也就是将元件转到PCB中,在这一节课,我们主要给大家讲解的是何在protel99se快速布线,我们在这节课当中 ...
- perl 第十四章 Perl5的包和模块
第十四章 Perl5的包和模块 by flamephoenix 一.require函数 1.require函数和子程序库 2.用require指定Perl版本二.包 1.包的定义 2.在包间切 ...
- BZOJ 1008 越狱 (组合数学)
题解:正难则反,从总数中减去全部相邻不相同的数目就是答案,n*(n-1)^(m-1):第一个房间有n中染色方案,剩下m-1个房间均只有n-1种染色方案,用总数减就是答案. #include <c ...