玩坏C++的多态机制

面向对象的三个特点,封装继承多态,好了,当面向对象遇上指针,就有了C++。三个特点中的封装继承很容易理解,按笔者的个人理解,封装继承是为多态而生。当一个父类有多个继承类时,通过对父指针赋予不同继承类的对象,就可以灵活地调用继承类中的继承函数了,这就是多态。

虚函数是实现多态的重要元素,请看:

class A
{
public:
    void a0(){cout <<"a0"<<endl;}
    virtual void a1(){cout <<"a1"<<endl;}
};

class B: public A
{
public:
    void a0(){cout <<"b.a0"<<endl;};
    void a1(){cout <<"b.a1"<<endl;};
};

main()
{
    A * a0 = new B ();
    a0->a0();
    a0->a1();
    A a1 = B();
    a1.a0();
    a1.a1();    delete a;}

输出是a0, b.a1, a0, a1。喻示了两点:其一,C++多态是通过指针来实现的,这和Java中通过类型转换(C#中称为装箱和拆箱)不同,因为执行A a1=B()时,首先调用了B de 构造函数构造出B对象,然后调用A的复制构造函数构造A,因此,最终调用的是A的复制构造函数,在调用函数时当然也调用A的函数了;其二,virtual的功能是使用多态时,子类的同名同参数的函数得以覆盖父类函数,而对于非虚函数,C++中在通过对象调用成员函数时,函数的入口在编译时就静态地确定了,而编译器是不在乎指针在赋值后会指向什么对象的。

这一切来自于C++的虚函数表机制。虚函数表是一个连续的内存空间,保存着一系列虚函数指针。在构造一个子对象时,内存空间最开始的4B保存一个虚函数表的入口地址。如上例中,A的虚函数表为<A::a1>,B继承A并重写了虚函数a1,因此B的虚函数表为<B::a1>,即在继承的时候,用B::a1的函数地址覆盖了A::a1的地址。于是有了下面的代码:

class A
{
public:
    void a0(){cout <<"a0"<<endl;}
    virtual void a1(){cout <<"a1"<<endl;}
    virtual void a2(){cout <<"a2"<<endl;}
};

class B: public A
{
public:
    void a0(){cout <<"b.a0"<<endl;};
    void a1(){cout <<"b.a1"<<endl;};
    void a2(){cout <<"b.a2"<<endl;}
};

type void ( * Function)();

main()
{
    A * a = new B ();
    Function p =  (void (*)(void))*( (int *) *(int*)(a) + 0 );
    p();
    delete a;
}

其中:

  a是对象的地址,

  (int *) a是对象最开始4字节的地址

  * (int *)a是对象的最开始的4字节

  (int *) * (int *)a是对象最开始的四个字节,实际上是个地址,是什么地址呢,是虚函数表存放的地址

  * (int *) * (int *)a是虚函数表的第一项,也就是第一个虚函数的地址,因此+0表示第一个函数,+1表示第二个函数,以此类推

  于是通过函数指针p,成功地访问了B的第一个虚继承函数a1。虚函数表就是这么一回事。下面两个小魔术来了。

1,通过函数指针访问子类的私有函数

2,通过函数指针访问父类的私有函数

class A
{
    virtual void a0()
    {
        printf("A::a0 (private)\n");
    }
public:
    explicit A(){}
    virtual void a1()
    {
        printf("A::a1 (public)\n");
    }
};

class B : public A
{
public:
    explicit B(){}
private:
    int y;
    virtual void a1()
    {
        printf("B::a1 (private)\n");
    }
};

typedef void (* Function)();

main()
{
    A * a = new B();
    Function p;
    p = ( void (*)(void) ) *( (int *)  *(int*)(a) + 0 );
    p();
    p = (Function) *( (int*) *(int*)(a) + 1 );
    p();    a->a1();
    delete a;
}

其中A的虚函数表是<private A::a0, public A::a1>, B的虚函数表是<private A::a0, private B::a1>。

在第一次调用时p指向private A::a0,而第二次调用时p指向private B::a1。权限的检查是在编译阶段,因此动态的指针调用绕过了权限检查。

在第三次调用时(a->a1())时,由于对权限的检查是在编译阶段,而编译器不检查a到底指向什么对象(因为这是动态的),只看a的类型。编译器发现a是A的指针,而a1()在类A中是public函数,因此权限检查顺利地pass。随后开始执行,此时a->a1()的指针指向B::a1(),于是乎,我们成功地用父类指针A * a调用了子类B的私有函数。

时间: 2024-08-06 04:05:38

玩坏C++的多态机制的相关文章

Github 恶搞教程(一起『玩坏』自己的 Github 吧)

最近在伯乐在线读到一篇趣文,<如何在 Github『正确』做贡献>,里面各种能人恶搞 Github 的『Public contributions』,下面截取几个小伙伴的战绩: 顺藤摸瓜,发现原来有人已经做出『玩坏』Github 的工具啦,名叫 gitfiti.主要对应预先定义的模板,进行相应日期的 commit 操作,push 至 Github 后在贡献栏中生成相应像素点,并且利用 Github 贡献数不同颜色深度不同的机制,就可以在自己的贡献栏里面看见像素画了.怎么样,是不是心动啦,那么下面

众筹是被玩坏的C2B

什么叫"众筹"? 我们先来看看京东众筹对"众筹"的阐述. "众筹"译自crowdfunding,即大众筹资或群众筹资,是一种通过互联网方式向网友募集项目资金的模式.从行业来看,国内的众筹还是一个新兴行业,处在初创阶段.众筹的通俗说法就是,彼此成就梦想,大家筹钱完成一个任务.和预售.团购的区别是:预售是B2C,众筹偏向C2B,就是出资人先有购买或投资的意愿,筹资人按需组织生产. 众筹具有三点特征:1. 排他性,即处于众筹期限内的产品仅在单一渠道发售

玩坏JVM很简单--toString的递归调用

在JVM的内存管理机制下很少发生内存溢出的情况.至少我碰见的少,好像在SSH我多次发布项目时候出现过一次.今天看见一个特简单的方法让内存溢出(好吧,我似乎作死了--!): 1 public class InfiniteRecursion { 2 public String toString(){ 3 return "InfiniteRecursion address : " + this + "\n" ; 4 } 5 public static void main(

从共享健身仓到共享马扎,看共享经济如何被玩坏

看到现在共享经济项目遍地开花,其实很多行业都捶胸顿足感叹自己诞生地太早了.就像网吧,其实也能说是共享经济--提供电脑解决大众上网需求,并占据他们的time和money.而如今,"共享"已经被完全玩坏了,披着"共享"的外衣却有着各种小九九的项目层出不穷. 共享健身仓.共享马扎--这些打着"共享"名号的项目,或者是名不副实,或者是有着其他目的.而最终的结果,就是让业界.投资者和大众谈"共享"即色变.对于共享经济的破坏,什么时候才能

如何在VS上用C#玩坏“Hello World”。

如何在VS上用C#玩坏“Hello World”. 为了开发win8应用,重装了系统到win8.1,安好了VS2013终极版,我们开始使用C#,今天来玩“Hello World”. 首先,打开vs,建立一个控制台应用程序.我的建立路径是C:\Users\ZhangXiao\Desktop\,工程名字是ConsoleHelloWorld. 如图,打出代码行,调用System命名空间中Console类的WriteLine函数,在控制台上输出一句话. 第一种方法,打开cmd命令提示符,用cd命令进入C

那些年被我玩坏的点子和创意-下篇

时光如白驹过隙,继那些年被我玩坏的点子和创意-上篇已过一个月,平常忙活着和队友一起做产品了,挤得时间总结了下篇,分四个故事来说 自动发微博软件为啥做这个呢,淘宝客接口还没有限制以前好多玩淘宝客的.其中我发现有个淘宝客玩的挺特别的,他是在新浪微博和腾讯微博(当时还没有关闭),上通过时光机等一些定时发微博的平台给粉丝推送淘客商品信息,我还记得他的微博马甲叫"型男服装搭配",他推送的微博都是从淘宝男装男鞋等类目下的按销量等某些筛选条件后的top N条商品,我不知道这样效果怎么样,但我知道可以

漫谈可视化Prefuse(四)---被玩坏的Prefuse API

这个双12,别人都在抢红包.逛淘宝.上京东,我选择再续我的“漫谈可视化”系列(好了,不装了,其实是郎中羞涩...) 上篇<漫谈可视化Prefuse(三)---Prefuse API数据结构阅读有感>主要介绍了可视化工具Prefuse API中主要解读的是prefuse.data包中的主要接口,并利用<漫谈可视化Prefuse(一)---从SQL Server数据库读取数据>中例子,将参数配置模块剥离出来,实现界面传值,绘制图形. 本篇决定不再贴API,实在没啥意思,还占篇幅(但是不

Java类的多态机制

Java中将一个方法调用同一个方法主体关联起来被称作绑定.绑定分为前期绑定和后期绑定.前期绑定是在编译器决定的,而后期绑定是在程序运行时决定的.Java中除了static方法和final方法(private方法也是final方法,只不过是隐式的为final)之外,其他所有的方法都是后期绑定.Java类的多态指的是当将子类向上转型为父类型并调用父类型中相应的方法时,多态机制会根据动态绑定自动判断出调用相应的子类的方法.也就是说多态机制的存在的基础是子类实现了对父类中相应方法的函数覆盖.比如有一个S

科技不该被玩坏!宏碁携自拍帽登时装周

虽然平时我们总能见到许多奇葩的人.事.物,但那些似乎都还情有可原--比如凤姐这么奇葩就是为了出名.在这个奇葩层出不穷的时代,我们似乎已经有了很强的免疫力,对什么都见怪不怪.但总有一些表现得太过分的事物是我们所不能容忍的,比如科技产品中的奇葩.科技产品可以不实用,可以价格高,可以很小众,但绝不能被玩坏!而近日,宏碁却无视了这些底线,将旗下的科技产品彻底玩坏. 宏碁推出一款造型怪异的自拍帽子,完全超出了业界和消费者的想象,让我和围观的小伙伴脑洞大开.但"欣赏"完之后,却丝毫没有购买的冲动,