虚函数 c private f1 it 众所周知,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中依次存储了B::WhoAmIForB的偏移地址,A::WhoAmI的偏移地址。
注意了,关键就在这里: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 0;
}