C++语言:深入理解C++内存布局
小标 2018-07-10 来源 : 阅读 575 评论 0

摘要:本文主要向大家介绍了C++语言:深入理解C++内存布局,通过具体的内容向大家展示,希望对大家学习C++语言有所帮助。

本文主要向大家介绍了C++语言:深入理解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;

};</typename></iostream>

   


   

void main()

{

    CPoint<double> m_Point;

    std::cout <<"CPoint:"<< sizeof(m_Point) << std::endl;

    std::cin.get();

}</double>

   


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

 

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

 

m_Point对象的内存布局如上图所示,可以看到m_Point内部除了三个成员变量之外,还有一个_vfptr,_vfptr是一个虚函数表的指针,保存的是虚函数表的地址。m_Point内部一共有5个虚函数,所以对应的虚函数表中便有5个与虚函数对应得地址。<喎�"https://www.2cto.com/kf/ware/vc/" target="_blank">vcD4KPHA+08nT2tDpuq/K/bHt1rjV69W8vt00uPbX1r3ao6yyosfStKbT2sDgtcTE2rTmtdjWt8bwyry0pqOsy/nS1NX7uPbA4NK7ubLVvL7dMzK49tfWvdqhozwvcD4KPHA+PGJyPgo8L3A+CjxwPjIuMrv5wOC6rNPQ0Om6r8r9o6zKudPDxtXNqLzMs9CjrMXJyfrA4NbQsru6rNDpuq/K/TwvcD4KPHA+0N64xMnPw+a1xLT6wuujrLXDtb3I58/CtcTE2sjdPC9wPgo8cD48cHJlIGNsYXNzPQ=="brush:java;">#include template 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 class CPoint2D : public CPoint { 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();

}</double></double></double></pre><br>

<p></p>

<pre class="brush:java;"></pre>

<p></p>

<p><br>

</p>

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

<p></p>

<p><img src="https://www.2cto.com/uploadfile/Collfiles/20160625/20160625094847335.png" alt="\" style="width: 630px; height: 143.615px;"></p>

最后一个输出的是一个函数指针的大小,在没有虚继承的情况下,在X86(Win 32Debug)系统上输出是4.

<p></p>

<p>整个类的大小为32字节,我们看一下内存分布就明白了</p>

<p><img src="https://www.2cto.com/uploadfile/Collfiles/20160625/20160625094847336.png" alt="\" style="width: 630px; height: 370.752px;"><br>

</p>

<p>可以看到m_Point2D的内存布局和m_Point的内存布局很类似。一个虚函数表指针,然后三个成员变量。虚函数表中的内容和m_Point中的一摸一样。这是因为CPoint2D

 是从CPoint继承过来的。</p>

<p>2.3基类含有虚函数,使用普通继承,派生类中含有虚函数</p>

<p>继续修改上面的代码,得到如下的内容</p>

<p></p><pre class="brush:java;">#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();

    }

 

    virtual T getZ() const

    {

        std::cout << "CPoint2D:"<<sizeof(cpoint2d<t>::getZ()) << std::endl;

        return 0;

    }

     

    virtual void setZ(T newZ = 0)

    {

        //std::cout << "CPoint2D:" << sizeof(CPoint2D::setZ()) << std::endl;

        _z = 0;

    }

};</sizeof(cpoint2d<t></t></typename></typename></iostream></pre><pre class="brush:java;">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();

}</double></double></double></pre>上面的代码输出内容如下所示:<p></p>

<p></p>

<p><br>

</p>

 

<p><img src="https://www.2cto.com/uploadfile/Collfiles/20160625/20160625094847337.png" alt="\" style="width: 630px; height: 150.275px;"></p>

内存布局如下:

<p></p>

<p><img src="https://www.2cto.com/uploadfile/Collfiles/20160625/20160625094847338.png" alt="\" style="width: 630px; height: 387.1px;"><br>

</p>

<p>输出的内容和之前派生类中没有虚函数的一样,但是内存布局发生了变化。变化体现在_vfptr中,_vfptr中有4个地址是和CPoint中的一样,2个不一样,这是因为在CPoint2D中,重写了CPoint中的两个虚函数,从而派生类中的虚函数覆盖了父类中的虚函数。这地方的重写不仅仅是函数名相同,还要保证函数的参数类型,参数个数,函数的返回形式也和基类中的一致。</p>

<p>从上面的例子中我们可以得出以下的结论:</p>

<p>1)、类中一旦出现虚函数,编译器便会给其分配一个虚函数表,虚函数表指针的大小和编译器有关。</p>

<p>2)、派生类中如果对父类的虚函数进行了重写,那么派生类中的虚函数会覆盖父类的虚函数,体现在上图的虚函数表中的地址发生了变化。</p>

<p>3)、虚函数表指针总是处于类的地址的开始处,所以在计算类的大小时要注意这一点。</p>

<p><br>

</p>

<p>2.4基类不含有虚函数,使用虚继承,派生类中不含虚函数</p>

<p>这一次使用前一章节的代码,对前一章节的代码进行修改,得到如下的内容</p>

<p></p><pre class="brush:java;">#include <iostream>

using namespace std;

 

class CBase

{

    //public

public:

    CBase()

    {

 

    }

};

 

class CBaseClass

{

    //private members

private:

    int nCount;

 

    //public members

public:

 

    //private member funcs

private:

    CBaseClass(const CBaseClass &base)

    {

 

    }

 

    CBaseClass &operator = (const CBaseClass& base)

    {

        return *this;

    }

 

    //public members

public:

    CBaseClass(int count = 0)

    {

        nCount = count;

    }

    ~CBaseClass()

    {

 

    }

};

 

class CBaseClassNew

{

    //private members

private:

    int nCount;

 

    //public members

public:

    int nNewCount;

    //private member funcs

private:

    CBaseClassNew(const CBaseClassNew &base)

    {

 

    }

 

    CBaseClassNew &operator = (const CBaseClassNew& base)

    {

        return *this;

    }

 

    //public members

public:

    CBaseClassNew(int count = 0)

    {

        nCount = count;

    }

    ~CBaseClassNew()

    {

 

    }

};

 

class CDerivedClass : virtual public CBaseClass

{

    //private members:

private:

    int nDeriveCount;

 

    //public members

public:

    int nCurrentNum;

 

    //private member funcs

private:

    CDerivedClass(const CDerivedClass& derived)

    {

 

    }

 

    CDerivedClass & operator = (const CDerivedClass &derived)

    {

        return *this;

    }

    //public member funcs

public:

    CDerivedClass(int nDerived = 0)

    {

        nDeriveCount = nDerived;

        nCurrentNum = 0;

    }

 

};</iostream></pre><br>

<pre class="brush:java;">void main()

{

    CBase base;

    cout << "base Size:" << sizeof(base) << endl;

 

    CBaseClass baseClass(10);

    cout << "baseClass Size:" << sizeof(baseClass) << endl;

 

    CDerivedClass derivedClass(12);

    cout << "derivedClass Size:" << sizeof(derivedClass) << endl;

 

    cin.get();

     

}</pre><br>

上述代码的输出内容如下<p></p>

<p><img src="https://www.2cto.com/uploadfile/Collfiles/20160625/20160625094847339.png" alt="\" style="width: 630px; height: 154.42px;"><br>

</p>

<p>CBase

 中只有一个构造函数,所以占一个字节</p>

<p>CBaseClass中有一个成员变量,为int型,所以占4个字节</p>

<p>CDerivedClass中自身的2个成员变量和基类中的1个成员变量均是int型,一共12个字节。CDerivedClass使用的是虚继承,这导致在派生类中会产生一个指针指向基类,所以派生类的大小为14字节。</p>

<p>其内存分布如下图所示:</p>

<p><img src="https://www.2cto.com/uploadfile/Collfiles/20160625/20160625094847340.png" alt="" style="width: 630px; height: 302.029px;"><br>

</p>

<p>因为篇幅太长,剩下的内容后面再说了。</p>

    

<p></p>                    

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言C/C+频道!


本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程