虚函数表分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ww1473345713/article/details/85539100

0.多态

C++几个的抽象、封装、继承和多态几大特性当中,多态是最为重要的一个。所谓多态(这里指狭义的多态)就是父类指针或引用指向子类对象,然后可以通过父类指针或引用调用子类的成员函数。 刚开始学习多态的时候,觉得多态非常神奇,同时也非常费解。后来了解到c++的多态是通过虚函数表来实现的,但是一直也没有做一个系统的总结。今天写几个例子梳理一下c++是怎么通过虚函数表来实现多态的。

1. 单继承虚函数表

例1

#include<iostream>
using namespace std;
class A {
    private:
        int  a;
    public:
        virtual void f() {
            cout<<"A::f()"<<endl;
        }
        virtual void g() {
            cout<<"A::g()"<<endl;
        }
};
class B:public A {
    private:
        int b;
    public:
        virtual void f() {
            cout<<"B::f()"<<endl;   
        }
        virtual void g1() {
            cout<<"B::g1()"<<endl;
        }
        void h() {
            cout<<"B::h()"<<endl;
        }
};
int main()
{
    typedef void(*fun)(void);
    fun pFun;
    A a;
    B b;
    return 0;
}

定义了两个对象,B继承自A。 B重写了A的f()函数,并新增了一个虚成员函数g1()和一个普通的成员函数h()。那么对象a,b的内存布局应该如下图所示:

在这里插入图片描述
口说无凭,我们用gdb打印一下看看。

$ gdb a.exe
GNU gdb (GDB) 7.6.1
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "mingw32".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from F:\zkangHUST\C++\a.exe...done.
(gdb) start
Temporary breakpoint 1 at 0x40146e: file test3.cpp, line 32.
Starting program: F:\zkangHUST\C++/a.exe
[New Thread 10860.0x2e0c]
[New Thread 10860.0x3e64]
[New Thread 10860.0x3e94]
[New Thread 10860.0x8]

Temporary breakpoint 1, main () at test3.cpp:32
32          A a;
(gdb) n
33          B b;
(gdb)
51          return 0;
(gdb) p a
$1 = {_vptr.A = 0x405178 <vtable for A+8>, a = 4194432}
(gdb) p (int*)*((int*)0x405178)
$2 = (int *) 0x403c08 <A::f()>
(gdb) p (int*)*((int*)0x405178 + 1)
$3 = (int *) 0x403c3c <A::g()>
(gdb) p (int*)*((int*)0x405178 + 2)
$4 = (int *) 0x0
(gdb) p b
$5 = {<A> = {_vptr.A = 0x405188 <vtable for B+8>, a = 4200896}, b = 0}
(gdb) p (int*)*((int*)0x405188)
$6 = (int *) 0x403ca0 <B::f()>
(gdb) p (int*)*((int*)0x405188+1)
$7 = (int *) 0x403c3c <A::g()>
(gdb) p (int*)*((int*)0x405188+2)
$8 = (int *) 0x403cd4 <B::g1()>
(gdb) p (int*)*((int*)0x405188+3)
$9 = (int *) 0x3a434347
(gdb) 

在这里插入图片描述
说明一下 ,gdb中执行 p A的结果是

(gdb) p a
$1 = {_vptr.A = 0x405178 <vtable for A+8>, a = 4194432}
(gdb) p (int*)*((int*)0x405178)
$2 = (int *) 0x403c08 <A::f()>

a的虚函数表地址是0x405178,把这个地址强制转换成int指针,对改指针取值即是虚函数表第一个函数的地址,可以转换成int指针,打印出来。可以看到,虚函数表跟我们分析的是一样的。这里有一个问题,可以看到A的虚函数表是以空地址结束的,B的虚函数结束的位置是一个随机值,可见虚函数表并不一定是以空地址结束。另外,B类新增的h()函数没有加入到虚函数表中,因为它不是一个虚函数,这个函数怎么调用已经在程序编译的过程中确定了(即所谓静态联编,也叫早期联编)。
同理,如果有第三个类C像下面这样继承类B。

class C:public B {
    private:
        int c;
    public:
        virtual void f() {
            cout<<"C::f()"<<endl;   
        }
        virtual void g1() {
            cout<<"C::g1()"<<endl;
        }
        virtual void k() {
            cout<<"C::k()"<<endl;
        }
};

那么C对象的内存应该如下图:
在这里插入图片描述

int main()
{
    A a;
    B b;
    C c;
    return 0;
}

gdb打印结果如下:

(gdb) p c
$1 = {<B> = {<A> = {_vptr.A = 0x4051c0 <vtable for C+8>, a = 1948871853}, b = 4200912}, c = 6422368}
(gdb) p (int*)*((int*)0x4051c0)
$2 = (int *) 0x403d58 <C::f()>
(gdb) p (int*)*((int*)0x4051c0 + 1)
$3 = (int *) 0x403c4c <A::g()>
(gdb) p (int*)*((int*)0x4051c0 + 2)
$4 = (int *) 0x403dc0 <C::g1()>
(gdb) p (int*)*((int*)0x4051c0 + 3)
$5 = (int *) 0x403d8c <C::k()>
(gdb) p (int*)*((int*)0x4051c0 + 4)
$6 = (int *) 0x3a434347

在这里插入图片描述

2. 多继承(无虚函数覆盖)

单继承的虚函数表比较简单,现在来看下多继承的虚函数表是什么样的。首先看多继承无虚函数覆盖的情况。假设有四个类A,B,C,D。继承关系如下图。
在这里插入图片描述

代码如下:

class A {
    private:
        int  a;
    public:
        virtual void f() {
            cout<<"A::f()"<<endl;
        }
        virtual void g() {
            cout<<"A::g()"<<endl;
        }
};
class B {
    private:
        int a;
    public:
        virtual void f() {
            cout<<"B::f()"<<endl;   
        }
        virtual void g() {
            cout<<"B::g()"<<endl;
        }
};
class C {
    private:
        int a;
    public:
        virtual void f() {
            cout<<"C::f()"<<endl;   
        }
        virtual void g() {
            cout<<"C::g1()"<<endl;
        }
};
class D:public A,public B, public C {
    private:
        int a;
    public:
       virtual void h() {
           cout<<"D::h()"<<endl;
       }
};

子类继承了多个父类,在内存中会维持多张虚函数表,有几个父类就有几张虚函数表。同时,自己新加的虚函数会附加到第一个父类的虚函数表后面。类D的内存布局如图:

在这里插入图片描述
gdb 打印的结果是:

(gdb) p d
$1 = {<A> = {_vptr.A = 0x4051f0 <vtable for D+8>, a = 6422368}, <B> = {_vptr.B = 0x405204 <vtable for D+28>, a = 4200896}, <C> = {
    _vptr.C = 0x405214 <vtable for D+44>, a = 3981312}, a = 4194432}
(gdb) p (int*)*((int*)0x4051f0)
$2 = (int *) 0x403c08 <A::f()>
(gdb) p (int*)*((int*)0x4051f0 + 1)
$3 = (int *) 0x403c3c <A::g()>
(gdb) p (int*)*((int*)0x4051f0 + 2)
$4 = (int *) 0x403d88 <D::h()>
(gdb) p (int*)*((int*)0x4051f0 + 3)
$5 = (int *) 0xfffffff8
(gdb) p (int*)*((int*)0x405204)
$6 = (int *) 0x403c88 <B::f()>
(gdb) p (int*)*((int*)0x405204 + 1)
$7 = (int *) 0x403cbc <B::g()>
(gdb) p (int*)*((int*)0x405204 + 2)
$8 = (int *) 0xfffffff0
(gdb) p (int*)*((int*)0x405214)
$9 = (int *) 0x403d08 <C::f()>
(gdb) p (int*)*((int*)0x405214 + 1)
$10 = (int *) 0x403d3c <C::g()>
(gdb) p (int*)*((int*)0x405214 + 2)
$11 = (int *) 0x3a434347
(gdb)

猜你喜欢

转载自blog.csdn.net/ww1473345713/article/details/85539100