c++对象的内存布局

引言

大概有接近一年的时间没有怎么用过c++了, 大多数时间都在学习关于linux以及linux C的知识, 一直想总结一些c++的知识却总不知怎么写, 从哪里入手, 前几月重新整理了一些关于c++类的内存布局的验证实验就想着正好可以用于切入口。

环境

这些实验都是在vs2017 x86环境下实现的, 自己也在x64以及g++中尝试过, 只能通过编译, 运行就会段错误,索性我就只用vs来实现了。为了更加直观的看到类的内存布局, 请先对vs进行必要的配置vs中c++类的内存分布调试环境

对象模型

在深度探索c++对象模型的书中提到过关于c++对象模型有简单对象模型, 表格驱动对象模型, c++对象模型三种。

简单对象模型现主要应用于指向成员的指针, 也就类似于虚表的作用。

在这里插入图片描述

表格驱动对象模型主要应用于虚函数, 即虚指针。

在这里插入图片描述

c++对象模型应用于c++类的内存布局。

在这里插入图片描述

虚指针的内存布局

在类中关于虚指针的存放位置有两种,根据编译器的不同有可能存放位置就不同, 一种是存放在类的前端(与c不兼容), 另一种是存放在类的尾端(与c兼容)。 前者是一般编译器都是采用的, 我使用的环境都是前者。

虚表

类中只存放了一个虚指针用来指向内存中虚表的位置, 而虚函数都是存放在虚表中, 这样不管类中有多少个虚函数就只需要一个虚指针就行了。虚表就是一个数组指针(说是二维数组还是有点欠缺, 毕竟数组指针跟二位数组还是有区别), 数组中每个指针就指向一个虚函数的地址。

类内存布局的验证

现在我们就来简单的对类的内存分布在vs中做一个简单的验证。

#include <iostream>
#include <stdlib.h>
#include <cstring>
#include <functional>

using std::cin; using std::cout; using std::string; using std::endl;
typedef int value_type;

typedef class Point2
{
public:
	Point2(value_type x = 0, value_type y = 0) : x(x), y(y) {}

	virtual void print1() { cout << "printf1" << endl; }
	virtual void print2() { cout << "printf2" << endl; }
	void print() { cout << x << endl; }
private:
	virtual void print3() { cout << "printf3" << endl; }

private:
	value_type x, y;
	static value_type num;
}Point2;

value_type Point2::num = 0;

int main() 
{
    system("pause");
}

类的布局情况我们能够清楚的明了, 虚指针放在了类的前端接着才存放类的其他成员变量, 同样也能清楚该类的大小为什么是8字节, 注意静态成员并不存放在类中; 接着再来看下面的虚表,vpt[0]指向print1虚函数, vpt[1]指向print2虚函数。

在这里插入图片描述

现在我们就验证了虚函数在类中的布局, 同时也证明了虚表的存在。

直接调用虚函数

上面我们验证了虚函数在类中的布局, 现在我们直接通过一个函数指针来调用虚函数, 甚至我们还可以直接调用私有函数, 私有成员。

将上面的代码添加以下代码即可 :

typedef void(*Fun)(void);

int main()
{
	Point2 point(1, 1);
	std::function<void(void)> fun;
	fun = (Fun)*((int*)*(int*)(&point) + 0);
	fun();	// printf1  

	fun = (Fun)*((int*)*(int*)(&point) + 1);
	fun();	// printf2
	// 直接调用私有的虚函数
	fun = (Fun)*((int*)*(int*)(&point) + 2);
	fun();	// printf3

	point.print();	// 1
	value_type x;
	x = *((int*)&point + 1) = 2;
	point.print();	// 2
	cout << x << endl;	// 2

	system("pause");
	exit(0);
}

在这里插入图片描述

从运行结果证实了我们通过一个普通指针访问了printf3这个私有虚函数, 还修改了x这个私有成员变量的值。

如果你对上面的(Fun)*((int*)*(int*)(&point) + 0)写法很难明了的话, 前面说了虚表就是一个数值指针, 那么也可以这样修改代码。这样就清楚明了了。

typedef void(*Fun)(void);
int **vptr = NULL;

int main()
{
	Point2 point(1, 1);
	vptr = (int**)&point;
	std::function<void(void)> fun;
	fun = (Fun)vptr[0][0];
	fun();

	fun = (Fun)vptr[0][1];
	fun();
	// 直接调用私有的虚函数
	fun = (Fun)vptr[0][2];
	fun();

	point.print();
	value_type x;
	x = *((int*)&point + 1) = 2;
	point.print();
	cout << x << endl;

	system("pause");
	exit(0);
}

总结

本节之所以涉及到修改类中的私有成员, 只是为了让人对类的内存布局有一个更深的印象而已, 但是这种做法无法在太多编译器中实现, 因为这样做就完全忽视了封装性原则, 所以64和g++都是我发运行的。

本节也只是对c++的虚函数和虚表有了一个认识了解, 还有好几个点都没有去涉及, 比如类大小的计算, 静态成员为什么不算在类的内存布局中, 还有多态又是怎样通过虚表实现的… 这些都是下面我们来分析的问题了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值