3
#include<iostream>

using namespace std;

class X
{
    int a;
    int b;
    public:
    void f(int a)
    {
        cout<<"\nInside X";
    }


    virtual void abc ()
    {
        cout<<"\nHello X";
    }

};


class Y : public X
{
    int a;
    public:

    void f(int a, int b)
    {
        cout<<"\nInside Y";
    }


    void abc()
    {
        cout<<"\nHello Y";
    }

};



int main()
{

    X a;

    cout<<sizeof(X);

    Y b;

    cout<<sizeof(Y);

    X *h = new Y;

    h->abc();
}

我知道类 X 的大小为 12 字节的原因是因为它包含指向虚拟表的 vptr(虚拟指针)。无论如何我可以读取这个虚拟表,如果没有,至少可以访问虚拟指针。我尝试使用联合,但它给了我某种错误。

另外,当我调用 h->abc() 时,它如何知道类的对象h指向?我认为这大部分是在编译时完成的。但是当你有一个指向派生类的基类指针时,它怎么知道要执行哪个类函数。考虑这两种情况

X *h = new X;
h->abc();/* This would call the abc function in X */

X *h = new Y;
h->abc();/* This would call the abc function in Y*/

我读到,h指针将转到它所指向的对象的 vtable 并因此调用该函数?但是这是如何在运行时实现的呢?

4

3 回答 3

6

好的,您的第一个问题:我给您举一个可能更好理解的例子! 在此处输入图像描述

#include<iostream>
using namespace std;
class Base1 {

    public:

        int ibase1;

        Base1():ibase1(10) {}

        virtual void f() { cout << "Base1::f()" << endl; }

        virtual void g() { cout << "Base1::g()" << endl; }

        virtual void h() { cout << "Base1::h()" << endl; }
};

class Base2 {

    public:

        int ibase2;

        Base2():ibase2(20) {}

        virtual void f() { cout << "Base2::f()" << endl; }

        virtual void g() { cout << "Base2::g()" << endl; }

        virtual void h() { cout << "Base2::h()" << endl; }

};

class Base3 {

    public:

        int ibase3;

        Base3():ibase3(30) {}

        virtual void f() { cout << "Base3::f()" << endl; }

        virtual void g() { cout << "Base3::g()" << endl; }

        virtual void h() { cout << "Base3::h()" << endl; }
};

class Derive : public Base1, public Base2, public Base3 {

    public:

        int iderive;

        Derive():iderive(100) {}

        virtual void f() { cout << "Derive::f()" << endl; }

        virtual void g1() { cout << "Derive::g1()" << endl; }

};

这是实现三个基类 base1、base2、base3 的派生类的内存等级,其中您:

Base1 *p1 = new Derive();
Base2 *p2 = new Derive();
Base3 *p3 = new Derive();

p1 将指向 vtale1,p2 将指向 vtable2,p3 将指向 vtable3,如果您调用一些虚拟函数,它将找到非常虚拟的表并获取地址!

在您的代码中:

X *h = new Y;

h 会指向 Y 的内存的起始位置,也就是 X 的虚拟表,他会找到abc()Y 中实现的地址!

你的第二个问题:

编译器会把成员函数当作普通函数,所以把成员函数的地址放在code section里面,这样就不会占用内存了!!

如果你想读取虚拟表,你可以这样尝试:这是我在 gcc4.7 的示例中尝试过的

typedef void(*Func)(void);
    Derive d;
    int **pd = (int **)(&d);
    int i = 0;
    while(i < 4)
    {
        Func f = (Func)pd[0][i];
        f();
        i++;
    }
    int s = (int)(pd[1]);
    cout << s << endl;
    i = 0;
    cout << "===============================================" << endl;
    while(i < 3)
    {
        Func f = (Func)pd[2][i];
        f();
        i++;
    }
    s = (int)(pd[3]);
    cout << s << endl;
    cout << "===============================================" << endl;
    i = 0;
    while(i < 3)
    {
        Func f = (Func)pd[4][i];
        f();
        i++;
    }
    s = (int)(pd[5]);
    cout << s << endl;
    s = (int)(pd[6]);
cout << s << endl;

你会得到如下结果:

 Derive::f()
Base1::g()
Base1::h()
Derive::g1()
10
===============================================
Derive::f()
Base2::g()
Base2::h()
20
===============================================
Derive::f()
Base3::g()
Base3::h()
30
100
于 2013-07-31T03:42:53.210 回答
2
  1. 除非您确定自己在做什么,否则不应尝试访问 vtable 指针。就我们通常用来定义程序含义的语言而言,vtable 甚至不存在。这是一个实现细节,它属于实现(又名编译器和运行时环境)。

    如果实现受到可移植 ABI(应用程序二进制接口)的限制,那么 ABI 将说明在哪里可以找到 vtable 指针以及 vtable 中的内容。reinterpret_cast< vtable const * const & >( my_obj )应该从任何“合理”ABI 上的对象中获取指针。

    这样的程序将被限制在一个平台和一个 ABI 上。(C++ ABI 接口的变化往往比 C 多,但比其他语言少。)依赖 ABI 是一个糟糕的设计选择,除非你只是想证明你疯了。

  2. vtable 标识派生类——这就是它的目的。它包含指向由覆盖基类的派生类实现的函数的指针。它还包含一个带有派生类名称的结构并链接到它的基类,以便动态确定它是从什么派生的。

    用于确定派生和查找派生对象的算法dynamic_cast实际上可能非常慢——而不是 O(1)。它通常必须搜索基类的链接结构。

于 2013-07-31T03:41:58.097 回答
1

反正我可以读这个虚拟表吗

不是真的,不是不知道指针相对于对象指针值的位置,这是编译器特定的。

如果没有,至少可以访问虚拟指针。

为什么?您可以通过 获取函数的地址,h->abc这是您想要的吗?

另外,当我调用 h->abc() 时,它如何知道 h 指向的类的对象?

它不是真的,它只知道那个类的 vtable 在哪里。如果您使用 RTTI,则 vtable 中有信息告诉它是什么类,但调用虚函数不是必需的。从 X 派生的每个类都有自己的 vtable,其中包含自己的指针,用于自己的虚函数。(当然总是假设一个基于 vtable 的实现。)

我读到,h 指针将转到它所指向的对象的 vtable 并因此调用该函数?但是这是如何在运行时实现的呢?

你刚刚自己描述了。稍微详细说明一下,指针h->abc解析h->_vtable[x]为某个常量x,表示虚函数指针的 vtable 中的偏移量abc。所以调用解析为*(h->_vtable[abc])(...).

另一个问题,与我需要清理的虚拟功能无关。如果函数像任何其他变量一样具有地址,为什么它们不占用对象中的空间?

他们为什么要这样做?这意味着每个对象中每个函数的副本。重点是什么?

于 2013-07-31T03:43:28.660 回答