【VC++】虚函数 内存结构 - 第二篇(单继承,无虚函数覆盖)

【VC++】虚函数 内存结构 - 第二篇(单继承,无虚函数覆盖)

#include <IOSTREAM>
using namespace std;

class Base
{
public:
	int nBase1;
	int nBase2;
	
	Base(int n1,int n2):nBase1(n1),nBase2(n2)
	{
		cout<<"Base::Base("<<n1<<","<<n2<<")"<<endl;
	}
	
	virtual void F()
	{
		cout<<"Base::F()"<<endl;
	}
	virtual void G()
	{
		cout<<"Base::G()"<<endl;
	}
};

class Derive:public Base
{
public:
	int nDerive1;
	int nDerive2;
	
	Derive(int n1,int n2,int n3,int n4):Base(n1,n2),nDerive1(n3),nDerive2(n4)
	{
		cout<<"Derive::Derive("<<n3<<","<<n4<<")"<<endl;
	}
	
	virtual void F2()
	{
		cout<<"Derive::F2()"<<endl;
	}
	virtual void G2()
	{
		cout<<"Derive::G2()"<<endl;
	}
};


typedef void(*Fun)(void);

void TestBase(Base &b)
{
	Fun pFun = NULL;
	int** pVtab = (int**)&b;
	
	cout << "类对象地址 / 虚函数表指针的地址:" << (&b) << " / " << pVtab << endl;
	cout << "虚函数表指针值(虚函数表的地址)(十进制):" << (int)pVtab[0] <<endl;
	cout << "虚函数表指针值(虚函数表的地址)(十六进制):" << pVtab[0] <<endl; 
	
	cout << "虚函数表 — 第 1 个函数地址的地址:"  <<  &pVtab[0][0] << " , ";	//好比一个变量的地址
	cout << "函数地址值:"  << (int*)pVtab[0][0] << " 调:";	//好比一个变量的值(这个值也是个地址)
	pFun = (Fun)pVtab[0][0];
	pFun();
	
	cout << "虚函数表 — 第 2 个函数地址的地址:"  <<  &pVtab[0][1] << " , ";
	cout << "函数地址值:"  << (int*)pVtab[0][1] << " 调:";
	pFun = (Fun)pVtab[0][1];
	pFun();
	
	cout << "虚函数表 — 第 3 个函数地址的地址:"  <<  &pVtab[0][2] << " , ";
	cout << "函数地址值:"  << (int*)pVtab[0][2] <<endl ;	//如果仅有一个虚函数,后面的节点不是NULL???
	//pFun = (Fun)pVtab[0][2];
	//pFun();
	
	cout << "类对象 — 第 1 个变量地址:" << &pVtab[1]  << " 值:"<< (int)pVtab[1] <<endl;	
	cout << "类对象 — 第 2 个变量地址:" << &pVtab[2]  << " 值:"<< (int)pVtab[2] <<endl;
	cout << "类对象 — 第 3 个变量地址:" << &pVtab[3]  << " 值:"<< (int)pVtab[3] <<endl;	//之后的内存是什么???
	
	cout<<endl;
}

void TestDerive(Derive &d)
{
	Fun pFun = NULL;
	int** pVtab = (int**)&d;
	
	cout << "类对象地址 / 虚函数表指针的地址:" << (&d) << " / " << pVtab << endl;
	cout << "虚函数表指针值(虚函数表的地址)(十进制):" << (int)pVtab[0] <<endl;
	cout << "虚函数表指针值(虚函数表的地址)(十六进制):" << pVtab[0] <<endl; 
	
	cout << "虚函数表 — 第 1 个函数地址的地址:"  <<  &pVtab[0][0] << " , ";	//好比一个变量的地址
	cout << "函数地址值:"  << (int*)pVtab[0][0] << " 调:";	//好比一个变量的值(这个值也是个地址)
	pFun = (Fun)pVtab[0][0];
	pFun();
	
	cout << "虚函数表 — 第 2 个函数地址的地址:"  <<  &pVtab[0][1] << " , ";
	cout << "函数地址值:"  << (int*)pVtab[0][1] << " 调:";
	pFun = (Fun)pVtab[0][1];
	pFun();

	cout << "虚函数表 — 第 3 个函数地址的地址:"  <<  &pVtab[0][2] << " , ";
	cout << "函数地址值:"  << (int*)pVtab[0][2] << " 调:";
	pFun = (Fun)pVtab[0][2];
	pFun();

	cout << "虚函数表 — 第 4 个函数地址的地址:"  <<  &pVtab[0][3] << " , ";
	cout << "函数地址值:"  << (int*)pVtab[0][3] << " 调:";
	pFun = (Fun)pVtab[0][3];
	pFun();
	
	cout << "虚函数表 — 第 5 个函数地址的地址:"  <<  &pVtab[0][4] << " , ";
	cout << "函数地址值:"  << (int*)pVtab[0][4] <<endl ;	//如果仅有一个虚函数,后面的节点不是NULL???

	
	cout << "类对象 — 第 1 个变量地址:" << &pVtab[1]  << " 值:"<< (int)pVtab[1] <<endl;	
	cout << "类对象 — 第 2 个变量地址:" << &pVtab[2]  << " 值:"<< (int)pVtab[2] <<endl;
	cout << "类对象 — 第 3 个变量地址:" << &pVtab[3]  << " 值:"<< (int)pVtab[3] <<endl;
	cout << "类对象 — 第 4 个变量地址:" << &pVtab[4]  << " 值:"<< (int)pVtab[4] <<endl;
	cout << "类对象 — 第 5 个变量地址:" << &pVtab[5]  << " 值:"<< (int)pVtab[5] <<endl;	//之后的内存是什么???
	
	cout<<endl;
}

void main()
{
	Base b(111,222);
	Derive d(123,321,456,654);
	Derive d2(1,2,3,4);

	TestBase(b);
	TestDerive(d);
	TestDerive(d2);

	cout<<endl;
}

运行结果:
在这里插入图片描述

父类对象、子类对象,各种地址和值的比对(红色表示不同的地方):

在这里插入图片描述
同一子类,两个不同对象 的比对:

在这里插入图片描述

类图、监视值(可以看到,VS的监视值并不全面,还是以实际打印的为准):
在这里插入图片描述
在内存中的结构是:
在这里插入图片描述
在本文结束前,我也还是来介绍下自己吧。我从03年初学编程,一直到现在都在做软件开发方面的事,做过VC++、VB/Delphi/C++Builder、C#/Asp.Net、Java、Web前后端、数据库、服务器端、客户端、js、lua、Flash AS、cocos2dx/Unity3d……都有接触过,甚至做过很长一段时间的运营、产品策划、美工、客服、线下推广员、公司经理 多职务……技术只是一种工具,用来实现业务需求,所以最大的特长是逻辑思维、挖掘业务需求、强化用户体验、快速的产品实现,最自以为豪的,我做过的很多产品,很稳定,bug极少(当然也出现过没考虑周全的漏洞在所难免但总体上也还算少),有很多独立开发的游戏运营到现在还在稳定的运行。不是技术极客,而是业务需求带动技术的学习研究,所以也不怎么挑语言,编程语言大体上也都是相通的,底层原理也有很多类同的地方。所以缺点从不敢说精通哪一门语言。遇到小工具、小需求的订制实现,我个人最喜欢的还是用VC6,简洁干净,编译出来的东西 体积小,速度快,占CPU内存小。当然实际工作中,还是随项目。欢迎大家和我交流,我的QQ是:七6.肆-陆_柒-4`7_四.
生活中的我,热爱 街舞、飙车、花式游泳、游戏一条命通关……极限运动,我抖音号(一不小心玩出花样)是:1917940952

文章没有考虑字节对齐(都用默认的字节对齐),并且是在32位环境下,VC6、VS2017 都测试过。

	cout<<"sizeof(Base): "<<sizeof(Base)<<endl;
	cout<<"sizeof(Derive): "<<sizeof(Derive)<<endl;

在这里插入图片描述

题外扩展:可以禁止生成虚表。我们可以通过 __declspec(novtable) 来告诉编译器不要生成虚表,ATL中大量应用这种技术来减小虚表的内存开销,比如将本文的Base类改成:
class __declspec(novtable) Base
那么在调用 TestBase(b) 时会出现错误:
在这里插入图片描述
所以,需要把 TestBase(b) 注释掉后 //TestBase(b); 进行sizeof(Base)发现仍然是12,监视到类对象b的虚表指针仍在,但虚函数表已经不在了:
在这里插入图片描述

结论: 通过__declspec(novtable),我们只能禁止编译器生成虚表,但是不能阻止对象仍包含虚表指针(不能减小对象的大小),也不能阻止程序对虚表的访问(尽管实际虚表不存在),所以禁止生成虚表只适用于永远不会实例化的类(基类)。

(转载时请注明作者和出处。未经许可,请勿用于商业用途)
原创出处:https://blog.csdn.net/maoyeahcom/article/details/108771096

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值