class 关键字内存布局详解(二)

之前讲解了 class基类和单继承的内存布局,本章将一下多继承和虚基类的内存布局是怎样的。
先上多继承的代码:


#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <memory>
#include <windows.h>

class CBase
{
public:
	CBase() {
		printf("Init Base\r\n");
		strcpy(m_szText, "null");
	}

	~CBase() {
		printf("Uninit Base\r\n");
	}

	virtual void SetText(const char* strText) {
		strcpy(m_szText, strText);
	}

	void ShowText() {
		printf("%s\n", m_szText);
	}

protected:
	char m_szText[64];
};

class CName
{
public:
	CName() {
		printf("Init CName\r\n");
		strcpy(m_szName, "null");
	}

	~CName() {
		printf("Uninit CName\r\n");
	}

	virtual void SetName(const char* strText) {
		strcpy(m_szName, strText);
	}

	virtual void ShowName() {
		printf("name=%s\n", m_szName);
	}

protected:
	char m_szName[64];
};

class CAge
{
public:
	CAge() {
		printf("Init CAge\r\n");
		m_nAge = 0;
	}

	~CAge() {
		printf("Uninit CAge\r\n");
	}

	virtual void SetAge(int nAge) {
		m_nAge = nAge;
	}

	virtual void ShowAge() {
		printf("age=%d\n", m_nAge);
	}

protected:
	int m_nAge;
};

class CDerive : public CBase, public CName, public CAge
{
public:
	CDerive() :CBase(), CName(), CAge() {
		printf("Init CDerive\r\n");
		memset(m_szTitle, 0, sizeof(m_szTitle));
	}

	~CDerive() {
		printf("Uninit CDerive\r\n");
	}

	virtual void SetText(const char* strText) {
		sprintf(m_szText, "text=%s", strText);
	}

	virtual void SetAge(int nAge) {
		m_nAge = nAge + 3;
	}

	virtual void __stdcall SetTitle(const char* strText) {
		sprintf(m_szTitle, "%s", strText);
	}

	void SetTitle2(const char* strText)
	{
		sprintf(m_szTitle, "title=%s", strText);
	}

	void ShowTitle() {
		printf("%s\n", m_szTitle);
	}
private:
	char m_szTitle[64];
};


typedef void(__thiscall* set_text_ptr)(void* _this_ptr, const char* strText);
typedef void(__stdcall* set_title_ptr)(void* _this_ptr, const char* strText);
typedef void(__thiscall* set_age_ptr)(void* _this_ptr, int nAge);
typedef void(__thiscall* show_ptr)(void* _this_ptr);

typedef struct __TBaseVtbl {
	set_text_ptr pfnSetText;
}TBaseVtbl;

typedef struct __TNameVtbl {
	set_text_ptr pfnSetName;
	show_ptr pfnShow;
}TNameVtbl;

typedef struct __TAgeVtbl {
	set_age_ptr pfnSetAge;
	show_ptr pfnShow;
}TAgeVtbl;

typedef struct __TDeriveVtbl {
	set_text_ptr pfnSetText;
	set_title_ptr pfnSetTitle;
}TDeriveVtbl;

typedef struct __TBase {
	TBaseVtbl* pVtbl;
	char szText[64];
}TBase;

typedef struct __TName {
	TNameVtbl* pVtbl;
	char szName[64];
}TName;

typedef struct __TAge {
	TAgeVtbl* pVtbl;
	int nAge;
}TAge;

typedef struct __TDerive {
	union //CDerive 和 第一个继承的类的虚表位置是重合的
	{
		TDeriveVtbl* pVtbl;
		TBase Base;
	};
	TName Name; //第二个继承的类
	TAge Age; //第三个继承的类
	char szTitle[64];//最后才是CDerive的成员变量
}TDerive;


int main()
{
	printf("先进行常规的调用看看结果\n\n");
	CDerive* pDerive = new CDerive();

	pDerive->SetText("hello");
	pDerive->ShowText();

	pDerive->SetTitle("测试");
	pDerive->ShowTitle();

	pDerive->SetName("lili");
	pDerive->ShowName();

	pDerive->SetAge(5);
	pDerive->ShowAge();

	delete pDerive;

	printf("\n\n再测试一下结构体的内存布局是否正确");

	CDerive* pTemp = new CDerive();
	TDerive* pDerive2 = (TDerive*)pTemp;

	pDerive2->pVtbl->pfnSetText(pDerive2, "hello");
	pTemp->ShowText();

    //与单继承不同,多继承的this指针是需要偏移的,所以不能够直接传pDerive2
    //必须从派生类中偏移到对应的class指针进行传递
	pDerive2->Base.pVtbl->pfnSetText(&pDerive2->Base, "ShowText");
	pTemp->ShowText();

	pDerive2->pVtbl->pfnSetTitle(pDerive2, "测试");
	pTemp->ShowTitle();

	strcpy(pDerive2->szTitle, "测试2");
	pTemp->ShowTitle();

	pDerive2->Name.pVtbl->pfnSetName(&pDerive2->Name, "lili");
	pDerive2->Name.pVtbl->pfnShow(&pDerive2->Name);
	pTemp->ShowName();

	pDerive2->Age.pVtbl->pfnSetAge(&pDerive2->Age, 3);
	pDerive2->Age.pVtbl->pfnShow(&pDerive2->Age);
	pTemp->ShowAge();

	delete pTemp;
	//TDerive* pDerive2 = (TDerive* )pDerive;

	getchar();
	return 0;
}

有了<<class 关键字内存布局详解(一)>> 的基础,相信上面的代码不用解释大家都能够看懂了。当中比较需要留意的是this指针的偏移,在C++的class对象中,它会自己进行隐式转换,但是struct却不会,所以我们需要手动偏移到正确的 class对象的this指针,然后进行参数传递。其实单继承中也需要注意这个问题的,但单继承的派生类和基类的地址是覆盖和追加的,也就是说 this 指针偏移都是零,所以一般不额外进行偏移也没有关系,但是多继承除了第一个继承对象外,其他对象的this指针都不是零,必须进行额外的this指针转换。

然后我们再看看虚基类的内存布局:


#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <memory>
#include <windows.h>

class CBase
{
public:
	CBase() {
		printf("Init Base\r\n");
		strcpy(m_szText, "CBase");
	}

	~CBase() {
		printf("Uninit Base\r\n");
	}

	virtual void SetText(const char* strText) {
		strcpy(m_szText, strText);
	}

	void ShowText() {
		printf("%s\n", m_szText);
	}

protected:
	char m_szText[64];
};

class CName : virtual public CBase
{
public:
	CName() {
		printf("Init CName\r\n");
		strcpy(m_szName, "CName");
	}

	~CName() {
		printf("Uninit CName\r\n");
	}

	virtual void SetName(const char* strText) {
		strcpy(m_szName, strText);
	}

	virtual void ShowName() {
		printf("name=%s\n", m_szName);
	}

protected:
	char m_szName[64];
};

class CAge : virtual public CBase
{
public:
	CAge() {
		printf("Init CAge\r\n");
		m_nAge = -1;
	}

	~CAge() {
		printf("Uninit CAge\r\n");
	}

	virtual void SetAge(int nAge) {
		m_nAge = nAge;
	}

	virtual void ShowAge() {
		printf("Age=%d\n", m_nAge);
	}

protected:
	int m_nAge;
};

class CSex
{
public:
	CSex() {
		printf("Init CSex\r\n");
		strcpy(m_szSex, "CSex");
	};

	~CSex() {
		printf("Uninit CSex\r\n");
	}

	virtual void SetSex(const char* strText) {
		strcpy(m_szSex, strText);
	}

	virtual void ShowSex() {
		printf("sex=%s\n", m_szSex);
	}

protected:
	char m_szSex[64];
};

class CDerive : public CName, public CAge, public CSex
{
public:
	CDerive() {
		printf("Init CDerive\r\n");
		strcpy(m_szTitle, "Title");
	}

	~CDerive() {
		printf("Uninit CDerive\r\n");
	}

	virtual void __stdcall SetTitle(const char* strText) {
		sprintf(m_szTitle, "%s", strText);
	}

	void ShowTitle() {
		printf("%s\n", m_szTitle);
	}
private:
	char m_szTitle[64];
};


typedef void(__thiscall* set_text_ptr)(void* _this_ptr, const char* strText);
typedef void(__stdcall* set_title_ptr)(void* _this_ptr, const char* strText);
typedef void(__thiscall* set_age_ptr)(void* _this_ptr, int nAge);
typedef void(__thiscall* show_ptr)(void* _this_ptr);

typedef struct __TBtbl {
	int nOffset;
}TBtbl;

typedef struct __TBaseVtbl {
	set_text_ptr pfnSetText;
}TBaseVtbl;

typedef struct __TNameVtbl {
	set_text_ptr pfnSetName;
	show_ptr pfnShow;
}TNameVtbl;

typedef struct __TAgeVtbl {
	set_age_ptr pfnSetAge;
	show_ptr pfnShow;
}TAgeVtbl;

typedef struct __TSexVtbl {
	set_text_ptr pfnSetSex;
	show_ptr pfnShow;
}TSexVtbl;

typedef struct __TDeriveVtbl {
	set_text_ptr pfnSetName;
	show_ptr pfnShowName;
	set_title_ptr pfnSetTitle;
}TDeriveVtbl;

typedef struct __TBase {
	TBaseVtbl* pVtbl;
	char szText[64];
}TBase;

typedef struct __TName {
	TNameVtbl* pVtbl;
	TBtbl* pBtbl;
	char szName[64];
}TName;

typedef struct __TName2 {
	TNameVtbl* pVtbl;
	TBtbl* pBtbl;
	char szName[64];
	TBase Base;
}TName2;

typedef struct __TAge {
	TAgeVtbl* pVtbl;
	TBtbl* pBtbl;
	int nAge;
}TAge;

typedef struct __TSex {
	TSexVtbl* pVtbl;
	char szSex[64];
}TSex;

//class CDerive : public CName, public CAge, public CSex
typedef struct __TDerive {
	union //CDerive 和 第一个继承的类的虚表位置是重合的
	{
		TDeriveVtbl* pVtbl;
		TName Name;
	};
	TAge Age; //第二个继承的类
	TSex Sex; //第三个继承的类

	char szTitle[64];//CDerive的成员变量

	//最后才是虚继承的基类数据
	//虽然两个相同类型的union看起来怪怪的,用一个变量的写法也可以
	//但我认为这种写法,更好地表达了虚继承的思想
	union
	{
		TBase NameBase;
		TBase AgeBase;
	};
}TDerive;


int main()
{
	printf("先进行常规的调用看看结果\n\n");
	CDerive* pDerive = new CDerive();

	pDerive->SetText("hello");
	pDerive->ShowText();

	pDerive->SetTitle("测试");
	pDerive->ShowTitle();

	pDerive->SetName("lili");
	pDerive->ShowName();

	pDerive->SetAge(5);
	pDerive->ShowAge();

	pDerive->SetSex("女");
	pDerive->ShowSex();

	delete pDerive;

	printf("\n\n再测试一下结构体的内存布局是否正确");

	CDerive* pTemp = new CDerive();
	TDerive* pDerive2 = (TDerive*)pTemp;

	int a = sizeof(CBase);
	int b = sizeof(CName);
	int c = sizeof(CAge);
	int d = sizeof(CSex);
	int e = sizeof(CDerive);

	int a1 = sizeof(TBase);
	int b1 = sizeof(TName);
	int c1 = sizeof(TAge);
	int d1 = sizeof(TSex);
	int e1 = sizeof(TDerive);

	pDerive2->pVtbl->pfnSetTitle(pDerive2, "测试");
	pTemp->ShowTitle();
	printf("%s\n", pDerive2->szTitle);

	pDerive2->Name.pVtbl->pfnSetName(pDerive2, "lili");
	pDerive2->Name.pVtbl->pfnShow(pDerive2);
	pTemp->ShowName();
	printf("name=%s\n", pDerive2->Name.szName);

	pDerive2->Age.pVtbl->pfnSetAge(&pDerive2->Age, 5);
	pDerive2->Age.pVtbl->pfnShow(&pDerive2->Age);
	pTemp->ShowAge();
	printf("age=%d\n", pDerive2->Age.nAge);

	pDerive2->Sex.pVtbl->pfnSetSex(&pDerive2->Sex, "女");
	pDerive2->Sex.pVtbl->pfnShow(&pDerive2->Sex);
	pTemp->ShowSex();
	printf("sex=%s\n", pDerive2->Sex.szSex);

	//测试虚基类
	pDerive2->NameBase.pVtbl->pfnSetText(&pDerive2->NameBase, "pfnSetText");
	pTemp->ShowText();

	strcpy(pDerive2->AgeBase.szText, "pDerive2->Base2.szText");
	pTemp->ShowText();

	printf("Name=%p; age=%p; \n", &pDerive2->Name, &pDerive2->Age);

	//pBtbl->nOffset 在x86中一般为 -4, 就是当前指针位置往后偏移4位,就是派生类的 this指针
	//比如CName的 &pBtbl->nOffset + pBtbl->nOffset = CName
	printf("src=%p; base=%p; name=%d; age=%d\n",
		pDerive2, &pDerive2->NameBase, 
		pDerive2->Name.pBtbl->nOffset, 
		pDerive2->Age.pBtbl->nOffset);

	if (pDerive2->Name.pBtbl->nOffset + (char*)&pDerive2->Name.pBtbl == (char*)&pDerive2->Name)
	{
		printf("找到了派生类的 this 指针\n");
	}

	delete pTemp;

	getchar();
	return 0;
}

以上为 VC 中测试到的虚继承内存布局,可以看到虚基类的内容被放到了对象的尾部。要注意的一点是,C++中并没有规定说虚基类一定要放在对象的尾部,而且派生类的虚基类指针,也没必要是偏移量。也就是说,当你在非VC环境,比如 gcc 环境下,这个虚继承测试可能不通过(其他多继承这些是可以的)。由于我这边暂时缺乏 gcc 环境,有兴趣的同学可以自己测试一下。

class 关键字内存布局详解(三)
class 关键字内存布局详解(一)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值