Cpp7 C++的多态实现 -- 虚表

Cpp7 C++的多态实现 -- 虚表

多态的实现原理


#include "stdafx.h"

#include <stdio.h>
#include <windows.h>


class A
{
public:
    int x;
    virtual void Test()
    {
        printf("A \n");
    }
protected:
private:
};

class B:public A
{
public:
    int x;
    void Test()
    {
        printf("B \n");
    }
protected:
private:
};

void Fun(A* p)
{
    p->Test();
}

int main(int argc, char* argv[])
{
    A a;
    B b;

    Fun(&b);
    return 0;
}

//我们发现在这里 调用的test函数 是b的 因为fun方法传入的对象是b b继承自a 这里体现了多态

//反编译

31:   void Fun(A* p)
32:   {
00401050   push        ebp
00401051   mov         ebp,esp
00401053   sub         esp,40h
00401056   push        ebx
00401057   push        esi
00401058   push        edi
00401059   lea         edi,[ebp-40h]
0040105C   mov         ecx,10h
00401061   mov         eax,0CCCCCCCCh
00401066   rep stos    dword ptr [edi]
33:       p->Test();
00401068   mov         eax,dword ptr [ebp+8]
0040106B   mov         edx,dword ptr [eax]
0040106D   mov         esi,esp
0040106F   mov         ecx,dword ptr [ebp+8]
00401072   call        dword ptr [edx]    //间接调用 + 虚表
00401074   cmp         esi,esp
00401076   call        __chkesp (00401240)
34:   }


@ILT+0(?Fun@@YAXPAVA@@@Z):
00401005   jmp         Fun (00401050)
@ILT+5(??0B@@QAE@XZ):
0040100A   jmp         B::B (00401190)
@ILT+10(??0A@@QAE@XZ):
0040100F   jmp         A::A (00401100)
@ILT+15(?Test@B@@UAEXXZ):
00401014   jmp         B::Test (004011f0)
@ILT+20(?Test@A@@UAEXXZ):
00401019   jmp         A::Test (00401140)
@ILT+25(_main):
0040101E   jmp         main (004010a0)

总结:
1. 当我们在类中定义虚函数时,就会产生虚表
2. 多态的实现 间接调用+虚表

虚表

观察带有虚函数的对象大小

#include "stdafx.h"

#include <stdio.h>
#include <windows.h>


class A
{
public:
    int x;
    void Test()
    {
        printf("A \n");
    }
protected:
private:
};

int main(int argc, char* argv[])
{
    A a;
    printf("%d \n",sizeof(a));
    return 0;
}

//结果是4
#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

class A
{
public:
    int x;
    virtual void Test()
    {
        printf("A \n");
    }
protected:
private:
};

int main(int argc, char* argv[])
{
    A a;
    printf("%d \n",sizeof(a));
    return 0;
}

//结果是 8
#include "stdafx.h"

#include <stdio.h>
#include <windows.h>


class A
{
public:
    int x;
    virtual void Test()
    {
        printf("A \n");
    }
    virtual void Test1()
    {
        printf("A \n");
    }
protected:
private:
};

int main(int argc, char* argv[])
{
    A a;
    printf("%d \n",sizeof(a));
    return 0;
}

//结果还是8

发现:定义了虚函数 对象大小会多出4个字节,多个虚函数也只有多一个4字节

虚表的位置

#include "stdafx.h"

#include <stdio.h>
#include <windows.h>


class A
{
public:
    int x;
    virtual void Test()
    {
        printf("A \n");
    }
};

class B:public A
{
public:
    virtual void Test()
    {
        printf("B \n");
    }
};

void Fun(A* a)
{
    a->Test();
}

int main(int argc, char* argv[])
{
    A a;
    B b;
    Fun(&a);
    Fun(&b);
    return 0;
}

通过vc6的监视器发现a对象的具体结构

image

发现加了虚函数之后对象前面对了一个值 指向 0x00422fac

那么这里的值指向的就是虚表的位置

继续追踪
调出内存窗口查找此内存

image

里面的值是 00401028 (小端存储)

vc6中 ctrl +g 跳转到对应反汇编位置

image

这里指向的正好是A的test 方法
(此时多了个TEST1 是因为上次编译后的结果没有清理缓存 )

26:   void Fun(A* a)
27:   {
00401050   push        ebp
00401051   mov         ebp,esp
00401053   sub         esp,40h
00401056   push        ebx
00401057   push        esi
00401058   push        edi
00401059   lea         edi,[ebp-40h]
0040105C   mov         ecx,10h
00401061   mov         eax,0CCCCCCCCh
00401066   rep stos    dword ptr [edi]
28:       a->Test();
//取参数也就是a对象的指针 到eax
00401068   mov         eax,dword ptr [ebp+8] 
//读取eax也就是a对象的首地址 也就是虚表的位置 
0040106B   mov         edx,dword ptr [eax]  
0040106D   mov         esi,esp
//传递this指针,到ecx
0040106F   mov         ecx,dword ptr [ebp+8]
//调用虚表中记录的函数位置 这里是第一个就直接是edx
00401072   call        dword ptr [edx]
00401074   cmp         esi,esp
00401076   call        __chkesp (00401240)
29:   }

//虚表


00401023   jmp         A::A (004010d0)
00401028   jmp         A::Test (00401090)
0040102D   jmp         A::Test1 (00401110)
00401032   jmp         B::B (00401170)
00401037   jmp         Fun (00401050)
0040103C   jmp         B::Test (004011c0)
00401041   jmp         A::A (00401200)

虚表的结构

据观察,虚表中存储的都是函数地址,每个地址占用4个字节,有几个虚函数,则就有几个地址

虚表的内容

子类没有重写时的值

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

class A
{
public:
    int x;
    virtual void Test()
    {
        printf("A \n");
    }
};

class B:public A
{
public:
};

void Fun(A* a)
{
    a->Test();
}

int main(int argc, char* argv[])
{
    B b;
    Fun(&b);
    return 0;
}

//虚表
00401014   jmp         A::Test (00401140)

子类重写时的值

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

class A
{
public:
    int x;
    virtual void Test()
    {
        printf("A \n");
    }
};

class B:public A
{
public:
    virtual void Test()
    {
        printf("B \n");
    }
};

void Fun(A* a)
{
    a->Test();
}

int main(int argc, char* argv[])
{
    B b;
    Fun(&b);
    return 0;
}

//虚表

@ILT+15(?Test@B@@UAEXXZ):
00401014   jmp         B::Test (00401150)
@ILT+20(?Test@A@@UAEXXZ):
00401019   jmp         A::Test (004011e0)

析构函数问题

class A
{
private:
    int* a;
public:
    A()
    {
        a = new int[10];
    }
    ~A()
    {
        delete a;
    }
    int* get_a()
    {
        return a;
    }
}

class B:public A
{
private:
    int* b;
public:
    B()
    {
        b = new int[5];
    }
    ~B()
    {
        delete b;
    }
    int* get_arr(int flag)
    {
        if(flag == 1 )
        {
            return b;
        }
        else
        {
            return get_a();
        }
    }
}


int main()
{
    A* p = new B;
    delete p;
    return 0;
}

上述代码执行时,如果直接调用 指针类型是父类,那么只会执行父类的析构函数释放掉 int* a
而b类中的int* b却不会被释放掉

理论上最好的方式是 逐步往上调用所有的析构函数,这样才可以释放所有使用的内存

// _20180212.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <stdio.h>
#include <windows.h>

class A
{
private:
    int* a;
public:
    A()
    {
        a = new int[10];
    }
    virtual ~A()
    {
        delete a;
        printf("析构 A \n");
    }
    int* get_a()
    {
        return a;
    }
};

class B:public A
{
private:
    int* b;
public:
    B()
    {
        b = new int[5];
    }
    ~B()
    {
        delete b;
        printf("析构 B \n");
    }
    int* get_arr(int flag)
    {
        if(flag == 1 )
        {
            return b;
        }
        else
        {
            return get_a();
        }
    }
};


int main(int argc,char* argv[])
{
    A* p = new B;
    
    delete p;
    return 0;
}

这里 将父类的析构函数定义为虚函数,那么delete的时候 就会调用子类重写父类虚析构函数的析构函数(虽然名字不相同,但是会自动重写_编译器约定)

并且此时析构函数现实从下往上逐步执行

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容

  • 多态(1)静态多态与动态多态 什么是多态 从字面上理解就是多种形态的意思。而多态一词最初源自希腊语,其含义便是“多...
    kingZXY2009阅读 2,305评论 0 2
  • 前言 前面我们已经介绍过了C++中的类与函数,不熟悉的,可以去看看NDK开发---C++学习(三):类与函数(上)...
    zhang_pan阅读 502评论 0 3
  • 题目太大,驾驭不了,举2个例子谈谈。 一、宾馆安排的2个特点 1、退房超级方便:由于宾馆消费不再另计,所以退房程序...
    易查理阅读 1,095评论 0 0
  • 最近降温很快,昨天看窗外杉树叶都红了,突然想出去走走。于是约了c出门压马路。 门口一排无患子都黄了,车站那一排柚子...
    奚所以阅读 179评论 0 0
  • 在这个颜值崇拜的时代,长的漂亮无疑是巨大的天赋,这也算是人生最无奈的事情之一,因为我们无法选择自己的父母,无法决定...
    小嘻嘿阅读 256评论 2 1