C++ 内存布局:深入理解C++内存布局

Posted 毛毛在天涯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 内存布局:深入理解C++内存布局相关的知识,希望对你有一定的参考价值。

1、虚函数简介

      虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数。典型情况下,这一信息具有一种被称为vptr(virtual table pointer,虚函数表指针)的指针的形式。vptr 指向一个被称为 vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到 vtbl。当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的 vptr 指向的 vtbl,然后在 vtbl 中寻找合适的函数指针。

      虚拟函数的地址翻译取决于对象的内存地址,而不取决于数据类型(编译器对函数调用的合法性检查取决于数据类型)。如果类定义了虚函数,该类及其派生类就要生成一张虚拟函数表,即vtable。而在类的对象地址空间中存储一个该虚表的入口,占4个字节,这个入口地址是在构造对象时由编译器写入的。所以,由于对象的内存空间包含了虚表入口,编译器能够由这个入口找到恰当的虚函数,这个函数的地址不再由数据类型决定了。故对于一个父类的对象指针,调用虚拟函数,如果给他赋父类对象的指针,那么他就调用父类中的函数,如果给他赋子类对象的指针,他就调用子类中的函数(取决于对象的内存地址)。

2、C++中含有虚函数的内存分布

涉及到虚函数的内存分布往往比较复杂,除了考虑其本身所带来的额外的内存开销,还要考虑继承等所带来的问题。针对这一方面,我们按照如下的步骤逐一解决。

   1)、单个含有虚函数的类

   2)、基类含有虚函数,使用普通继承,派生类中不含虚函数

   3)、基类含有虚函数,使用普通继承,派生类中含有虚函数

   4)、基类不含有虚函数,使用虚继承,派生类中不含虚函数

   5)、基类不含虚函数,使用虚继承,派生类中含有虚函数

   6)、基类含有虚函数,使用虚继承,派生类中不含虚函数

   7)、基类含有虚函数,使用虚继承,派生类中含有虚函数

   8)、基类含有虚函数,使用虚继承,向下派生多次

   9)、基类含有虚函数,多继承


2.1 含有虚函数的单个类

#include <iostream>

template<typename T>
class CPoint
{
public:
	CPoint()
	{
		_x = 0;
		_y = 0;
		_z = 0;
	}

	virtual void setX(T newX)
	{
		//std::cout << "CPoint setX" << std::endl;
		_x = newX;
	}
	virtual void setY(T newY)
	{
		_y = newY;
	}
	virtual void setZ(T newZ = 0)
	{
		_z = newZ;
	}


	virtual T getX() const
	{
		return _x;
	}

	virtual T getY() const
	{
		return _y;
	}

	virtual T getZ() const
	{
		return _z;
	}

protected:
	T _x;
	T _y;
	T _z;
};

void main()
{
	CPoint<double> m_Point;
	std::cout <<"CPoint:"<<	sizeof(m_Point) << std::endl;
	std::cin.get();
}

上面的程序输出结果如下:


上述的代码输出为32,一方面和内存布局有关,另一方面还和内存对齐有关。类模板实例化为double,构建一个对象,对象中有三个数据成员,每个数据成员占8字节。


m_Point对象的内存布局如上图所示,可以看到m_Point内部除了三个成员变量之外,还有一个_vfptr,_vfptr是一个虚函数表的指针,保存的是虚函数表的地址。m_Point内部一共有5个虚函数,所以对应的虚函数表中便有5个与虚函数对应得地址。

由于虚函数表指针占据4个字节,并且处于类的内存地址起始处,所以整个类一共占据32个字节。


2.2基类含有虚函数,使用普通继承,派生类中不含虚函数

修改上面的代码,得到如下的内容

#include <iostream>

template<typename T>
class CPoint
{
public:
	CPoint()
	{
		_x = 0;
		_y = 0;
		_z = 0;
	}

	virtual void setX(T newX)
	{
		//std::cout << "CPoint setX" << std::endl;
		_x = newX;
	}
	virtual void setY(T newY)
	{
		_y = newY;
	}
	virtual void setZ(T newZ = 0)
	{
		_z = newZ;
	}


	virtual T getX() const
	{
		return _x;
	}

	virtual T getY() const
	{
		return _y;
	}

	virtual T getZ() const
	{
		return _z;
	}

protected:
	T _x;
	T _y;
	T _z;
};

template<typename T>
class CPoint2D :  public CPoint<T>
{
public:
	CPoint2D()
	{
		_x = 0;
		_y = 0;
		_z = 0;
	}

	CPoint2D(T x, T y, T z = 0)
	{
		_x = x;
		_y = y;
		_z = z;
	}

	CPoint2D(const CPoint2D &point2D)
	{
		_x = point2D.getX();
		_y = point2D.getY();
		_z = point2D.getZ();
	}

	const CPoint2D& operator = (const CPoint2D& point2D)
	{
		if (this == &point2D)
			return *this;

		_x = point2D.getX();
		_y = point2D.getY();
		_z = point2D.getZ();
	}

	void operator +(const CPoint2D& point2D)
	{
		_x += point2D.getX();
		_y += point2D.getY();
		_z += point2D.getZ();
	}

	void operator -(const CPoint2D &point2D)
	{
		_x -= point2D.getX();
		_y -= point2D.getY();
		_z -= point2D.getZ();
	}
};

<pre name="code" class="cpp">void main()
{
	CPoint<double> m_Point;

	CPoint2D<double> m_Point2D(0.0,0.0);
	std::cout <<"CPoint:"<<	sizeof(m_Point) << std::endl;
	std::cout <<"CPoint2D:"<< sizeof(m_Point2D)<< std::endl;
	std::cout <<"CPoint2D::getZ:"<< sizeof(&CPoint2D<double>::getZ) << std::endl;
	
	std::cin.get();
}

 


上面的代码输出得到如下的内容