【问题标题】:Object construction and Virtual pointer during mutiple inheritance多重继承期间的对象构造函数和虚拟指针
【发布时间】:2016-05-16 14:44:03
【问题描述】:
class Base1 { 
    virtual void fun1() { cout << "Base1::fun1()" << endl; } 
    virtual void func1() { cout << "Base1::func1()" << endl; } 
}; 
class Base2 { 
    virtual void fun1() { cout << "Base2::fun1()" << endl; } 
    virtual void func1() { cout << "Base2::func1()" << endl; } 
}; 

class Test:public Base1,public Base2
{
public:
    virtual void test(){cout<<"Test";}
};


typedef void(*Fun)(void); 

int main()
{
    Test objTest; 
    Fun pFun = NULL;

    pFun = (Fun)*((int*)*(int*)((int*)&objTest+0)+0); pFun(); 
    pFun = (Fun)*((int*)*(int*)((int*)&objTest+0)+1); pFun(); 

//The following isnt supposed to print Test::test() right?
    pFun = (Fun)*((int*)*(int*)((int*)&objTest+0)+2); pFun(); 

    pFun = (Fun)*((int*)*(int*)((int*)&objTest+1)+0); pFun(); 
    pFun = (Fun)*((int*)*(int*)((int*)&objTest+1)+1); pFun();


//Isnt the following supposed to print Test:test() because the order of   
construction   object is Base1 followed by construction of Base2 followed by 
construction of Test.

    pFun = (Fun)*((int*)*(int*)((int*)&objTest+1)+2); pFun(); 
}

Test 对象的 sizeof 为 8 个字节。 所以从这个例子中可以明显看出,该对象由两个 4 字节的 _vptr 组成。由于继承顺序是public Base1,public Base2,这意味着对象应该按以下方式制作:

| _vptr to class Base1 vTable | -->this Base1 vtable should have 2 elements.
| _vptr to class Base2 vTable | -->this Base2 vtable should have 3 elements.

但从代码 sn-p 看来,对象是这样制作的:

| _vptr to class Base1 vTable | -->this Base1 vtable should have 3 elements.
| _vptr to class Base2 vTable | -->this Base2 vtable should have 2 elements.

第一个 vptr 指向一个包含 3 个函数指针的数组(第一个指向 Base1::fun1(),第二个指向 Base1::func1(),第三个指向 Test::test())。

派生对象由 Base+Derived 组成。这意味着第一个字节块是 Base 对象,其余的是 Derived。 如果是这样,那么在我们的 objTest 示例中,第二个 _vptr 应该指向三个函数指针(第一个指向 Base2::fun1()Base2::func1()Test::test())。但是我们看到第一个_vptr 指向Test::test() 的函数指针。

问题:

1.这种行为是编译器特有的吗?

2. 标准是否提到了这种行为?还是我的理解完全错误?

【问题讨论】:

  • 所有这些都是未定义的行为,您使用int 表达式来读取未声明为int 的对象
  • 我不明白你的意思。我正在对 int 进行类型转换(这意味着我正在尝试将构成对象的那些字节读取为整数)。这有什么问题?
  • 这是不允许的,因为有一种叫做严格别名规则的东西。要尝试检查任意内存,您需要使用 char 或最好使用 unsigned char

标签: c++ multiple-inheritance vtable memory-layout vptr


【解决方案1】:

vtable 的构造方式绝对取决于编译器。当你有多重继承时会发生什么。特别是两个 vtable 的生成顺序......

除了 vtable 之外,还存储了其他内容 - 例如类型信息,因此可以进行动态转换。

标准所要求的只是“虚拟函数工作”(当然,描述“工作”的含义有数千个词),但它的实现方式完全取决于编译器[和 C++ 库在某种程度上可能]。

【讨论】:

  • C++ 库中唯一需要读取 vtable 数据的部分是 typeinfo (RTTI) 的实现。
【解决方案2】:

首先,请注意 vtables 超出了 C++ 标准的范围,因为该标准只关注符合标准的程序的行为,而不是实现如何实现。

所有已知的实现都使用 vtable 来实现虚函数和 RTTI,但在多重继承、协变返回和虚基等更“微妙”的特性中存在显着的细微差别。

在更简单的情况下,基类的 vtable 可以扩展,因为基类子对象与派生对象位于同一地址;基类称为“主基类”。

在更复杂的情况下,派生类有多个多态基类,每个基类都有自己的 vptr。一个基类将是主要基类,派生类将扩展其 vtable。对象的布局使得其他基类子对象在派生对象中的偏移量为非零,因此当通过此类非主基类的 vtable 调用虚函数时,this 指针将需要调整;该 vtable 中引用的函数需要一个指向基类子对象或完整对象的指针:

  • 在基类的 vtable 中引用的函数需要一个指向完整基对象的指针;
  • 派生对象的非主要基础 vtable 中引用的函数需要指向基础子对象的指针;为了获得正确的this 指针,他们需要对派生进行基础调整。

由于需要进行调整,通过非主库调用虚函数的效率略低。编译器会更喜欢使用主 vptr。

编译器将使用从其他基类继承的所有虚函数扩展主基类的 vtable。

【讨论】:

    猜你喜欢
    • 2015-06-21
    • 2017-12-05
    • 2020-04-29
    • 2013-10-24
    • 2018-04-18
    • 2021-06-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多