C++ 对象的内存布局—— 虚继承下的虚函数

C++ 对象的内存布局(下)这篇文章的“单一虚拟继承”和“钻石型虚拟继承”时的类内存布局讲得不太清楚,我有一处疑问,我用的是VS2005。因此记录一下。

类继承图例如以下:

这里:类B被类B1和B2虚拟继承,而B1和B2同一时候被D继承。

B1的f()、B2的f()覆盖了B的f();

D的f()覆盖了B1的f(),D的f1()覆盖了B1的f1()

D的f()覆盖了B2的f(),D的f2()覆盖了B2的f2()

类代码例如以下:

class B
{
    public:
        int ib;
        char cb;
    public:
        B():ib(0),cb(‘B‘) {}

        virtual void f() { cout << "B::f()" << endl;}
        virtual void Bf() { cout << "B::Bf()" << endl;}
};
class B1 :  virtual public B//虚拟继承,钻石型继承
{
    public:
        int ib1;
        char cb1;
    public:
        B1():ib1(11),cb1(‘1‘) {}

        virtual void f() { cout << "B1::f()" << endl;}
        virtual void f1() { cout << "B1::f1()" << endl;}
        virtual void Bf1() { cout << "B1::Bf1()" << endl;}

};
class B2:  virtual public B
{
    public:
        int ib2;
        char cb2;
    public:
        B2():ib2(12),cb2(‘2‘) {}

        virtual void f() { cout << "B2::f()" << endl;}
        virtual void f2() { cout << "B2::f2()" << endl;}
        virtual void Bf2() { cout << "B2::Bf2()" << endl;}

};

class D : public B1, public B2
{
    public:
        int id;
        char cd;
    public:
        D():id(100),cd(‘D‘) {}

		virtual void f() { cout << "D::f()" << endl;}
		virtual void f1() { cout << "D::f1()" << endl;}
        virtual void f2() { cout << "D::f2()" << endl;}
        virtual void Df() { cout << "D::Df()" << endl;}
};

(1)单一虚拟继承:B1虚拟继承自B

查看类B1的内存布局的代码例如以下:

{
	int** pVtab = NULL;
	Fun pFun = NULL;

	B1 bb1;

	pVtab = (int**)&bb1;

	cout << "sizeof(B1) = " << sizeof(B1) << endl;

	cout << "[0] B1::_vfptr->" << endl;
	for (int i = 0; ; i++)
	{
		pFun = (Fun)pVtab[0][i];
		cout << ‘\t‘ << "[" << i << "] 0x" << (int*)pFun << "\t";
		if (pFun == NULL)
		{
			cout << endl;
			break;
		}
		pFun();
	}

	cout << "[1] B1::_vbptr->" << endl;
	cout << "\t" << "[0] B1dB1bptrB1 " << pVtab[1][0] << endl;
	cout << "\t" << "[1] B1dB1bptrB " << pVtab[1][1] << endl;

	cout << "[2] B1::ib1 = " << (int)pVtab[2] << endl;
	cout << "[3] B1::cb1 = " << (char)pVtab[3] << endl;

	cout << "[4] = 0x" << (int*)pVtab[4]/*[0]*/ << endl;

	cout << "[5] B::_vfptr->" << endl;	//
	cout << ‘\t‘ << "[" << 0 << "] thunk = 0x" << (int*)pVtab[5][0] << endl;
	for (int i = 1; ; i++)
	{
		pFun = (Fun)pVtab[5][i];
		cout << ‘\t‘ << "[" << i << "] 0x" << (int*)pFun << "\t";
		if (pFun == NULL)
		{
			cout << endl;
			break;
		}
		pFun();
	}

	cout << "[6] B::ib = " << (int)pVtab[6] << endl;
	cout << "[7] B::ic = " << (char)pVtab[7] << endl;
}

代码输出了类B1的布局:

sizeof(B1) = 32

[0] B1::_vfptr->

[0] 0x0041106E  B1::f1()

[1] 0x004110AF  B1::Bf1()

[2] 0x00000000

[1] B1::_vbptr->

[0] B1dB1bptrB1 -4

[1] B1dB1bptrB 16

[2] B1::ib1 = 11

[3] B1::ic1 = 1

[4] = 0x00000000     //VC++中的一个NULL分隔符把B和B1的布局分开

[5] B::_vfptr->

[0] 0x00411055  //奇怪的地方:这个不是函数B1::f()的地址。由于运行这个地址的函数会出错,那么整个B1类的B1::f()虚函数在哪里呢?难道是“调整块”的地址?

[1] 0x004111F4  B::Bf()

[2] 0x00000000

[6] B::ib = 0

[7] B::ic = B

(2)钻石型虚拟继承

查看类D的内存布局的代码例如以下:

{
	int** pVtab = NULL;
	Fun pFun = NULL;

	D d;

	pVtab = (int**)&d;

	cout << "sizeof(D) = " << sizeof(D) << endl;

	cout << "[0] D::B1::_vfptr->" << endl;
	for (int i = 0; ; i++)
	{
		pFun = (Fun)pVtab[0][i];
		cout << ‘\t‘ << "[" << i << "] 0x" << (int*)pFun << "\t";
		if (pFun == NULL)
		{
			cout << endl;
			break;
		}
		pFun();
	}

	cout << "[1] D::B1::_vbptr->" << endl;
	cout << "\t" << "[0] DdB1bptrB1 " << pVtab[1][0] << endl;
	cout << "\t" << "[1] DdB1bptrB " << pVtab[1][1] << endl;

	cout << "[2] B1::ib1 = " << (int)pVtab[2] << endl;
	cout << "[3] B1::cb1 = " << (char)pVtab[3] << endl;

	cout << "[4] D::B2::_vfptr->" << endl;
	for (int i = 0; ; i++)
	{
		pFun = (Fun)pVtab[4][i];
		cout << ‘\t‘ << "[" << i << "] 0x" << (int*)pFun << "\t";
		if (pFun == NULL)
		{
			cout << endl;
			break;
		}
		pFun();
	}

	cout << "[5] D::B2::_vbptr->" << endl;
	cout << "\t" << "[0] DdB2bptrB2 " << pVtab[5][0] << endl;
	cout << "\t" << "[1] DdB1bptrB " << pVtab[5][1] << endl;

	cout << "[6] B2::ib2 = " << (int)pVtab[6] << endl;
	cout << "[7] B2::cb2 = " << (char)pVtab[7] << endl;

	cout << "[8] D::id = " << (int)pVtab[8] << endl;
	cout << "[9] D::cd = " << (char)pVtab[9] << endl;

	cout << "[10] = 0x" << (int*)pVtab[10]/*[0]*/ << endl;

	cout << "[11] D::B::_vfptr->" << endl;

	cout << ‘\t‘ << "[" << 0 << "] 0x" << (int*)pVtab[11][0] << endl;//奇怪的地方
	for (int i = 1; ; i++)
	{
		pFun = (Fun)pVtab[11][i];
		cout << ‘\t‘ << "[" << i << "] 0x" << (int*)pFun << "\t";
		if (pFun == NULL)
		{
			cout << endl;
			break;
		}
		pFun();
	}

	cout << "[12] B::ib = " << (int)pVtab[12] << endl;
	cout << "[13] B::ic = " << (char)pVtab[13] << endl;
}

代码输出了类D的布局:

sizeof(D) = 56

[0] D::B1::_vfptr->

[0] 0x00411163  D::f1()

[1] 0x004110AF  B1::Bf1()

[2] 0x004110D2  D::Df()

[3] 0x00000000

[1] D::B1::_vbptr->

[0] DdB1bptrB1 -4

[1] DdB1bptrB 40

[2] B1::ib1 = 11

[3] B1::cb1 = 1

[4] D::B2::_vfptr->

[0] 0x0041101E  D::f2()

[1] 0x00411159  B2::Bf2()

[2] 0x00000000

[5] D::B2::_vbptr->

[0] DdB2bptrB2 -4

[1] DdB1bptrB 24

[6] B2::ib2 = 12

[7] B2::cb2 = 2

[8] D::id = 100

[9] D::cd = D

[10] = 0x00000000   //VC++中的一个NULL分隔符把B和B1和B2的布局分开

[11] D::B::_vfptr->

[0] 0x004110C3    //奇怪的地方:这个不是函数D::f()的地址。由于运行这个地址的函数会出错,那么整个D类的D::f()虚函数在哪里呢?难道是“调整块”的地址?

[1] 0x004111F4  B::Bf()

[2] 0x00000000

[12] B::ib = 0

[13] B::cb = B

眼下我还不清楚是怎么回事,不知道是不是thunk之类的原因。

參考:C++ 对象的内存布局(下)

时间: 2024-10-07 04:57:34

C++ 对象的内存布局—— 虚继承下的虚函数的相关文章

多重虚继承下的对象内存布局

<深入C++对象模型>绝对是一本值得深读的一本书,书里多次出现一句话,“一切常规遇见虚继承,都将失效”.这是一个有趣的问题,因为C++标准容忍对象布局的实现有较大的自由,出现了各编译器厂商实现的方式不同. 今天谈谈visual studio2013多重虚继承下对象布局.有错不要客气,不要吝啬你的留言,请直接开喷. class y和class z都是从class x虚继承来的子类(也叫派生类),class A是class y和class z的多重继承子类.为了简化问题,下面的data membe

win x64下C++对象的内存布局的学习

这篇文章是在学习陈皓的一篇文章<C++对象的内存布局(上)>后,针对x64的下情况的学习笔记.他的文章的链接地址是:http://blog.csdn.net/haoel/article/details/3081328. 他的示例代码中有下列片段: 如果在vs2015社区版中以debug方式编译出x64版本的exe运行后会崩溃.经过调试发现在x64下地址是以unsigned long long表示的(如果测试发现,编译后台实际上会把unsigned long long 转换为unsigned _

C++ 对象的内存布局(上)

C++ 对象的内存布局(上) 陈皓 http://blog.csdn.net/haoel 点击这里查看下篇>>> 前言 07年12月,我写了一篇<C++虚函数表解析>的文章,引起了大家的兴趣.有很多朋友对我的文章留了言,有鼓励我的,有批评我的,还有很多问问题的.我在这里一并对大家的留言表示感谢.这也是我为什么再写一篇续言的原因.因为,在上一篇文章中,我用了的示例都是非常简单的,主要是为了说明一些机理上的问题,也是为了图一些表达上方便和简单.不想,这篇文章成为了打开C++对象模

C++ 对象的内存布局(上)

转自陈皓的博客 前言 在谈论虚函数表里,至少有以下这些内容没有涉及: - 有成员变量的情况. - 有重复继承的情况. - 有虚拟继承的情况. - 有钻石型虚拟继承的情况. 所以,这篇文章将会是<C++虚函数表解析>的一个续篇,也是一篇高级进阶的文章. 对象的影响因素 简而言之,我们一个类可能会有如下的影响因素: 成员变量 虚函数(产生虚函数表) 单一继承(只继承于一个类) 多重继承(继承多个类) 重复继承(继承的多个父类中其父类有相同的超类) 虚拟继承(使用virtual方式继承,为了保证继承

C++对象模型之详述C++对象的内存布局

在C++对象模型之简述C++对象的内存布局一文中.详细分析了各种成员变量和成员函数对一个类(没有不论什么继承的)对象的内存分布的影响,及详细解说了怎样遍历对象的内存,包含虚函数表.假设你在阅读本文之前.还没有看过C++对象模型之简述C++对象的内存布局一文,建议先阅读一下.而本文主要讨论继承对于对象的内存分布的影响,包含:继承后类的对象的成员的布局.继承对于虚函数表的影响.virtual函数机制怎样实现.执行时类型识别等. 因为在C++中继承的关系比較复杂.所以本文会讨论例如以下的继承情况: 1

VS中C++对象的内存布局

本文主要简述一下在Visual Studio中C++对象的内存布局,这里没有什么测试代码,只是以图文的形式来描述一下内存分布,关于测试的代码以及C++对象模型的其他内容大家可以参考一下陈皓先生的几篇博文以及网上的其他一些文章: <C++虚函数表解析>:http://blog.csdn.net/haoel/article/details/1948051 <C++对象的内存布局(上)>:http://blog.csdn.net/haoel/article/details/3081328

Java对象的内存布局

Java对象的内存布局:对象头(Header),实例数据(Instance Data),对齐填充(Padding):另外:不同的环境结果可能有差异,我所在的环境是HotSpot虚拟机,64位Windows. 对象头 对象头在32位系统上占用8bytes,64位系统上占用16bytes. System.out.println("sizeOf(new Object()) = " + sizeOf(new Object())); sizeOf(new Object()) = 16 实例数据

c++对象模型是什么,对象的内存布局和结构问题

在c++发明的初期对于c++对象模型的争论从来没有停止过直到标准委员会通过了最终的c++对象模型这件事情才变得尘埃落定.C++对象模型可能是最不需要去解释的,但是又是不得不去说的因为c++的入门最先接触的就是c++对象.在上个世纪一共有三种c++对象模型,它们的出现可以说是一个不断优化的过程最终只有我们目前看到的c++对象模型在使用.了解c++对象模型非常重要,了解之后对于对象的内存布局,内存大小,虚函数以及静态数据成员和成员函数的理解有非常巨大的帮助.言归正传,下面就来分别讨论c++的三种对象

java对象的内存布局(一):计算java对象占用的内存空间以及java object layout工具的使用

最近在学习java对象内存布局方面的一些知识,主要是想知道一个java对象到底占用多少内存空间,以及java对象在内存中到底是什么样子的.c/c++中的sizeof运算符能够方便地告诉我们一个变量占用的内存空间,但是在java中却没有直接提供这种机制.如果想获取java对象占用的内存大小,可以利用java的Instrumentation机制.java.lang.instrument.Instrumentation这个接口提供了getObjectSize(Object objectToSize),