C++继承的内存布局

参考:
钻石型继承模型的内存分布
C++ 虚函数表解析—陈皓改进版
C++ 对象的内存布局(上)—陈皓改进版
C++ 对象的内存布局(下)—陈皓改进版
c++继承中的内存布局
本文是以上文章的一个总结。

1、单一继承(非虚继承)

在这里插入图片描述在这里插入图片描述在这里插入图片描述
可见以下几个方面:
1)虚函数指针在最前面的位置。
2)成员变量根据其继承和声明顺序依次放在后面。
3)在单一的继承中,被overwrite的虚函数在虚函数表中得到了更新。

单一继承(非虚继承)代码,上面黑色背景的图片就是这个代码的运行结果。

2、多重继承(非虚继承)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们可以看到:
1) 每个父类都有自己的虚表指针
2) 子类的成员函数被放到了第一个父类的虚表指针指向的地址中。
3) 内存布局中,其父类布局依次按声明顺序排列。
4) 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

多重继承(非虚继承)代码,上面黑色背景的图片就是这个代码的运行结果。

3、单一继承(虚继承)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、重复继承(钻石继承/菱形继承)

4.1 非虚继承


下图中运行在32位系统中,所以指针占4个字节。
在这里插入图片描述
在这里插入图片描述
1)其中第一个vfptr指向的虚表地址是B1和D共享的,因此其中的函数接口应该覆盖了B1和D的虚函数,而第二个则只有B2的虚函数。
2)从图中可以看出,在派生类D中,存在着两份基类B的成员变量ib和cb,一份是B1继承而来的,另一份是B2继承而来的,这样可能会出现二义性编译错误。我们可以指定类作用域符::进行限定来消除二义性:

D d;
d.ib = 0;               //二义性错误
d.B1::ib = 1;           //正确
d.B2::ib = 2;           //正确

也可以在语言层面利用虚继承机制来解决。

4.2 虚继承

在这里插入图片描述
下图中运行在32位系统中,所以指针占4个字节。
在这里插入图片描述
从上图可以看出,重复继承时用虚继承,派生类D对象在内存中占有56字节,比之前不用虚继承多了8个字节(之前为48字节),主要是因为多了“基类虚表指针”“vtordisp域”
在这里插入图片描述
注意和非虚多重继承不同的是,
1)第一个虚表指针指向的内容只记录了B1和D的虚函数(不包括B);
2)第二个虚标指针指向的内容只记录了B2和D的虚函数(不包括B);
3)B的虚函数在最后的基类虚表中;
4)B1和B2部分各增加了一个基类虚表指针vbptr,指向后面的基类。

从图中可以看出,VC++编译器在实现虚拟继承时,在派生类的对象中安插了两个vbptr指针。因此,对每个继承自虚基类的类实例,将增加一个隐藏的“基类虚表指针”(vbptr)成员变量,从而达到间接计算基类虚表位置的目的。该变量指向一个全类共享的偏移量表,表中项目记录了对于该类而言,“虚基类表指针”与虚基类之间的偏移量
由上可以看出,B1虚基类表指针vbptr与虚基类B之间的偏移量是40字节,B2虚基类表指针vbptr与虚基类B之间的偏移量是24字节。第一项中-4的含义:表示的是vfptr和vbptr的距离,如果B1中没有虚函数的定义,这个地方就会是0。

注意到在虚拟继承的C++对象内存布局中,还有一个4个字节的vtordisp字段。如果虚继承中派生类重写了基类的虚函数,并且在构造函数或者析构函数中使用指向基类的指针调用了该函数,编译器会为虚基类添加vtordisp域。

5、附代码

5.1 单一继承(非虚继承)代码

#include <iostream>
#include <stdio.h>
#include <stdint.h>
using namespace std;
 
class Parent {
public:
	int iparent;
	Parent() :iparent(10) {}
	virtual void f() { cout << "Parent::f()" << endl; }
	virtual void g() { cout << "Parent::g()" << endl; }
	virtual void h() { cout << "Parent::h()" << endl; }
};
 
 
class Child : public Parent {
public:
	int ichild;
	Child() :ichild(100) {}
	virtual void f() { cout << "Child::f()" << endl; }
	virtual void g_child() { cout << "Child::g_child()" << endl; }
	virtual void h_child() { cout << "Child::h_child()" << endl; }
};
 
class GrandChild : public Child {
public:
	int igrandchild;
	GrandChild() :igrandchild(1000) {}
	virtual void f() { cout << "GrandChild::f()" << endl; }
	virtual void g_child() { cout << "GrandChild::g_child()" << endl; }
	virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; }
};


//https://blog.csdn.net/a3192048/article/details/82259966 

//typedef void(*Fun)(void);
// https://blog.csdn.net/u014221279/article/details/50978204
// 需要知道:对于一个函数glFun(),它的函数名 glFun 其实是一个指针,指向该函数在内存中存的地方 
// typedef  返回类型(*新类型)(参数表)
// typedef的功能是定义新的类型。第一句就是定义了一种 Fun 的类型,
// 并定义这种类型为指向某种函数的指针,这种函数以一个void为参数并返回void类型。

// intptr_t是为了跨平台,其长度总是所在平台的位数,所以用来存放地址。 
#define intptr_t long long
int main()
{
	typedef void(*Fun)(void);
	GrandChild gc;
	Fun pFun = NULL;
 
	// 运行的电脑是64位的,因此指针是64位的, intptr_t*其实就是long long* 
	printf("%p , %x\n", &gc, &gc); // %p输出完整的地址,以16进制输出时,会自动省略前面的0 
	// 在64位系统下, intptr_t*指针占8个字节,64位 
	cout << &gc << " <- 指针指向的地址,也是虚表指针地址" << endl;
	// 但是int类型只占4个字节,32位 
	cout << &gc.iparent << " <- 变量iparent地址" << endl;
	cout << &gc.ichild << " <- 变量ichild地址" << endl;
	cout << &gc.igrandchild << " <- 变量igrandchild地址" << endl;
	cout << endl;
	
	intptr_t *Vptr = (intptr_t *)&gc; // Vptr就为指针的起始地址
	
	cout << "[0] GrandChild::_vptr->                " << Vptr << endl;
	intptr_t *pVtab = (intptr_t*) *(Vptr + 0); //*(Vptr + 0), 解引用后为 虚表指针指向的值,也就是虚函数表地址 
	for (int i = 0; ; i++){
		pFun = (Fun)(*(pVtab + i));		//虚函数地址的指针(pVtab + i),指向的地址*(pVtab + i) 
//		pFun = (Fun)(pVtab[i]); // pFun = (Fun)(*(pVtab + i)) 的另一种写法 
		cout << "    [" << i << "] ";
		if (pFun == NULL){
			cout << pFun << endl;
			break;
		}
		pFun();
	}
	
	//在64位系统下,对于 intptr_t*类型的指针,intptr_t为long long,进行+1后,增加的是8个字节
	// 但是int类型是4个字节的,所以在 虚表指针地址+1后,将指针转换为 int*,这样+1的时候,增加的是4个字节 
	int* pV = (int*)(Vptr + 1);
	cout << "[1] Parent.iparent = " <<  *(pV + 0) << "                " << (pV + 0) << endl;
	cout << "[2] Child.ichild = " << *(pV + 1)  << "                 " << (pV + 1) << endl;
	cout << "[3] GrandChild.igrandchild = " << *(pV + 2)  << "      " << (pV + 2) << endl;	 
 	
 	cout << endl << endl;
	
//	int* pVtab2 = (int*)&gc; 
//	intptr_t* pVtab3 = (intptr_t*)&gc; 
//	printf("%p\n%p\n", pVtab2, pVtab3);
//	cout << "[0] GrandChild::_vptr      = " << &pVtab2 << endl; 
//	cout << "[1] offset + 1             = " << &pVtab2[1] << " value:" << *(int*)(&pVtab2[1]) << endl;
//	cout << "[2] offset + 2             = " << &pVtab2[2] << " value:" << *(int*)(&pVtab2[2]) << endl;
//	cout << "[3] offset + 3             = " << &pVtab2[3] << endl; 

 
	return 0;
}

5.2 多重继承(非虚继承)代码

#include <iostream>
#include <stdio.h>
#include <stdint.h>
using namespace std;
 
class Base1 {
public:
	int ibase1, int_a;
	char char_b;
	Base1() :ibase1(10), int_a(11), char_b('1') {}
	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; }
};
//https://blog.csdn.net/a3192048/article/details/82259966
int main()
{
	typedef void(*Fun)(void);
	Derive d;
	Fun pFun = NULL;
 
	intptr_t *Vptr = (intptr_t *)&d; // Vptr就为指针的起始地址
	intptr_t *pVtab = NULL;
	
	cout << "[0] Base1::_vptr->          " << Vptr + 0 << endl;
	pVtab = (intptr_t*) *(Vptr + 0);
	for (int i = 0; i < 4; i++){
		pFun = (Fun)(pVtab[i]);
		cout << "    [" << i << "] ";
		pFun();
	}
	int* pV = (int*)(Vptr + 1);
	cout << "[1] Base1.ibase1 = " << *(pV + 0) << "       " << (pV + 0) << endl;
	cout << "    Base1.int_a  = " << *(pV + 1) << "       " << (pV + 1) << endl;
	cout << "    Base1.char_b = " << *(pV + 2) - '0' << "        " << (pV + 2) << endl;
	
	cout << "[2] Base2::_vptr->          " << Vptr + 3 << endl; 
	// 这里为什么 Vptr + 3呢?这就涉及到内存对齐了,这里默认的对齐字节为8字节  https://www.cnblogs.com/zrtqsk/p/4371773.html 
	pVtab = (intptr_t*) *(Vptr + 3);
	for (int i = 0; i < 3; i++){
		pFun = (Fun)(pVtab[i]);
		cout << "    [" << i << "] ";
		pFun();
	}
	cout << "[3] Base2.ibase2 = " << *(Vptr + 4) << "       " << (Vptr + 4) << endl;

	cout << "[4] Base3::_vptr->          " << Vptr + 5 << endl;
	pVtab = (intptr_t*) *(Vptr + 5);
	for (int i = 0; i < 3; i++){
		pFun = (Fun)(pVtab[i]);
		cout << "    [" << i << "] ";
		pFun();
	}
	pV = (int*)(Vptr + 6);
	cout << "[5] Base3.ibase3 = " << *(pV + 0) << "       " << (pV + 0) << endl;
	cout << "[6] Derive.iderive = " << *(pV + 1) << "    " << (pV + 1) << endl;

	
	cout << sizeof(d) << endl << endl;  // 输出 56 
	
	d.f();
	d.g1();
//	d.g();	// 没有重写,而且因为有多个父类都有g()函数,不能明确用哪个 
	Base3 *p = &d;
	p->g();
//	p->g1();  // Base3没有g1()
	Base1 *pp = &d; 
//	pp->g1(); // Base1也没有g1(),子类新增加的,父类是不能调用的 


	return 0;
}

5.3 改变虚函数表的代码

#include <iostream>
#include <stdio.h>
#include <stdint.h>
using namespace std;

class animal
{
protected:
  int age_;
  animal(int age): age_(age) { }

public:
  virtual void print_age(void) = 0;
  virtual void print_kind() = 0;
  virtual void print_status() = 0;
};

class dog : public animal
{
public:
  dog(): animal(2) { }
  ~dog() { }

  virtual void print_age(void) {
    cout << "Woof, my age = " << age_ << endl;
  }

  virtual void print_kind() {
    cout << "I'm a dog" << endl;
  }

  virtual void print_status() {
    cout << "I'm barking" << endl;
  }
};

class cat : public animal
{
public:
  cat(): animal(1) { }
  ~cat() { }

  virtual void print_age(void) {
    cout << "Meow, my age = " << age_ << endl;
  }

  virtual void print_kind() {
    cout << "I'm a cat" << endl;
  }

  virtual void print_status() {
    cout << "I'm sleeping" << endl;
  }
};

void print_random_message(void* something) {
  cout << "I'm crazy" << endl;
}
//https://www.zhihu.com/question/29256578/answer/43725188 
int main(void)
{
  cat kitty;
  dog puppy;
  animal* pa = &kitty;
  
  intptr_t* cat_vptr = *((intptr_t**)(&kitty));
  intptr_t* dog_vptr = *((intptr_t**)(&puppy));

  intptr_t fake_vtable[] = {
    dog_vptr[0],         // for dog::print_age
    cat_vptr[1],         // for cat::print_kind
    (intptr_t) print_random_message
  };
  // ((intptr_t**) pa) 是虚表指针,  *解引用后,为 虚函数表 地址 
  // 改变了指向的虚函数表 
  *((intptr_t**) pa) = fake_vtable;

  pa->print_age();    // Woof, my age = 1
  pa->print_kind();   // I'm a cat
  pa->print_status(); // I'm crazy

  return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值