Inside C++ object model: Object lessons

C++ 对象模型

C++中的数据成员有两种:static和non-static;函数成员有三种:static, non-static, virtual。各种类型的成员是如何在对象的内存空间中存放的?

  1. non-static成员变量:存放在对象的内存空间中。
  2. static成员变量:存放在对象的内存空间之外。
  3. static成员函数:存放在对象的内存空间之外。
  4. non-static成员函数:存放在对象的内存空间之外。
  5. virtual成员函数:对象的内存空间中存储一个虚函数表的指针,该指针指向一个虚函数表,虚函数表中存放虚函数的地址,虚表的第一项通常存储这个类对象的RTTI信息,及runtime type identification。

对于下面的这个Point类,其各种成员的在内存中的分布如下图所示:

class Point
{
public:
    int x;
    static int count;

public:
    Point();
    void Move();
    static int GetCount();
    virtual void Print();
    virtual ~Point();
};
Point类对象的内存分布

验证:通过clangg可以看到类对象的内存空间。(clang -Xclang -fdump-record-layouts Point.cc)

*** Dumping AST Record Layout
         0 | class Point
                           0 |   (Point vtable pointer)
8 |   int x
                                          | [sizeof=16, dsize=12, align=8,
                              |  nvsize=12, nvalign=8]

可以看到,在内存中前8个字节存储的是虚表指针。

继承体系下的类内存空间

1. 派生类只有一个基类

        当派生类只有一个基类的时候,派生类的前8个字节存储的是一个虚表指针,派生类的虚函数表与基类的虚函数表不是一个表,但是派生类的虚函数表会存有基类虚函数表的地址,如果派生类重写了基类的虚函数,则在派生类的虚函数表中,会用派生类的虚函数地址替换掉基类的虚函数地址。

#include <iostream>

using namespace std;

class Point
{
public:
    virtual void Show() { cout << "Show in Point" << endl; }
    virtual void Draw() { cout << "Draw in Point" << endl; }
    int pos;
};

class Plane : public Point
{
public:
    virtual void Draw() { cout << "Draw in Plane" << endl; }
    virtual void Print() { cout << "Print in Plane" << endl; }
    int area;
};



int main()
{
    Point p;
    Plane pl;
    cout << "Address of vtable of Point " << *(long*)(&p) << endl;
    cout << "Address of vtable of Plane " << *(long*)(&pl) << endl;

    long pointShowAddr = *(long*)*(long*)(&p);
    long pointDrawAddr = *((long*)*(long*)(&p)+1);

    long planeShowAddr = *(long*)*(long*)(&pl);
    long planeDrawAddr = *((long*)*(long*)(&pl)+1);
    long planePrintAddr = *((long*)*(long*)(&pl)+2);

    cout << "Address of function Show in class Point is " << pointShowAddr << endl;
    cout << "Address of function Draw in class Point is " << pointDrawAddr << endl;
    
    cout << "Address of function Show in class Plane is " << planeShowAddr << endl;
    cout << "Address of function Draw in class Plane is " << planeDrawAddr << endl;
    cout << "Address of function Print in class Plane is " << planePrintAddr << endl;

    ((void(*)(void))pointShowAddr)();
    ((void(*)(void))pointDrawAddr)();

    ((void(*)(void))planeShowAddr)();
    ((void(*)(void))planeDrawAddr)();
    ((void(*)(void))planePrintAddr)();

    return 0;
}
// 以上代码的输出
/*
    Address of vtable of Point 4351221832
    Address of vtable of Plane 4351221880
    
    Address of function Show in class Point is 4351217376
    Address of function Draw in class Point is 4351217440
    
    Address of function Show in class Plane is 4351217376
    Address of function Draw in class Plane is 4351217568
    Address of function Print in class Plane is 4351217632
    
    Show in Point
    Draw in Point
    
    Show in Point
    Draw in Plane
    Print in Plane
 */

此时,类对象的内存空间如下图所示:

        

如果Point有一个private成员 int dos; 则此时在Plane的内存空间中也会有一个dos,其位置在pos的下面一个slot。虽然Plane对象的内存空间中存在这个private这个成员,但是其无法访问。

2. 当派生类继承两个基类时,此时派生类中会有两个虚函数表指针。

#include <iostream>

using namespace std;

class Point
{
public:
    Point() : pos(99) {}
    virtual void Show() { cout << "Show in Point" << endl; }
    virtual void Draw() { cout << "Draw in Point" << endl; }
    int pos;
};

class Line
{
public:
    virtual void Extend() { cout << "Extend in Line" << endl; }
    int len;
};

class Plane : public Point, public Line
{
public:
    virtual void Draw() { cout << "Draw in Plane" << endl; }
    virtual void Print() { cout << "Print in Plane" << endl; }
    int area;
};



int main()
{
    Point p;
    Line l;
    Plane pl;
    cout << "Address of vtable of Point " << *(long*)(&p) << endl;
    cout << "Address of vtable of Line " << *(long*)(&l) << endl;
    cout << "Address of vtable of Plane " << *(long*)(&pl) << endl;

    long pointShowAddr = *(long*)*(long*)(&p);
    long pointDrawAddr = *((long*)*(long*)(&p)+1);

    long lineExtendAddr = *(long*)*(long*)(&l);

    long planeShowAddr = *(long*)*(long*)(&pl);
    long planeDrawAddr = *((long*)*(long*)(&pl)+1);
    long planePrintAddr = *((long*)*(long*)(&pl)+2);


    long planeExtendAddr = *(long*)*((long*)(&pl)+2); // 两个虚表之间还有一个成员pos,所以这里指针要在pl地址的基础上移动两个单位

    cout << "Address of function Show in class Point is " << pointShowAddr << endl;
    cout << "Address of function Draw in class Point is " << pointDrawAddr << endl;

    cout << "Address of function Extend in class Line is " << lineExtendAddr << endl;

    cout << "Address of function Show in class Plane is " << planeShowAddr << endl;
    cout << "Address of function Draw in class Plane is " << planeDrawAddr << endl;
    cout << "Address of function Print in class Plane is " << planePrintAddr << endl;
    cout << "Address of function Extend in class Plane is " << planeExtendAddr << endl;

    ((void(*)(void))pointShowAddr)();
    ((void(*)(void))pointDrawAddr)();

    ((void(*)(void))lineExtendAddr)();

    ((void(*)(void))planeShowAddr)();
    ((void(*)(void))planeDrawAddr)();
    ((void(*)(void))planePrintAddr)();

    ((void(*)(void))planeExtendAddr)();

    return 0;
}

// 以上代码的输出为
/*
    Address of vtable of Point 4417945680
    Address of vtable of Line 4417945728
    Address of vtable of Plane 4417945768
   
    Address of function Show in class Point is 4417940976
    Address of function Draw in class Point is 4417941040
    
    Address of function Extend in class Line is 4417941136
    
    Address of function Show in class Plane is 4417940976
    Address of function Draw in class Plane is 4417941280
    Address of function Print in class Plane is 4417941344
    Address of function Extend in class Plane is 4417941136

    Show in Point
    Draw in Point
    Extend in Line
    
    Show in Point
    Draw in Plane
    Print in Plane
    Extend in Line
*/

        此时,类Plane 同时继承了Point和Line,那么此时在Plane中会有两个虚函数表指针,第一个虚表指针所指向的虚函数表存放了类Point和类Plane的虚函数的地址;而第二个虚函数表指针所指向的虚函数表存放的是类Line的虚函数地址(如果类Plane重写了类Line的虚函数,则重写后的虚函数地址存放在第二个虚表中)。此时类对象的内存空间如下:

 也通过clang可以看到Plane的内存空间:

                                        *** Dumping AST Record Layout
                                                 0 | class Plane
                                                 0 |   class Point (primary base)
                                                 0 |     (Point vtable pointer)
                                                 8 |     int pos
                                               16 |   class Line (base)
                                               16 |     (Line vtable pointer)
                                               24 |     int len
                                               28 |   int area
                                                   | [sizeof=32, dsize=32, align=8,
                                                   |  nvsize=32, nvalign=8]

为什么不让两个虚表指针紧挨着排列呢?或者合并成一个虚表呢?

        猜测是为了进行上行转换时比较方便。基于这一现象,当一个派生类指针上行转化成基类指针时,通过该基类指针调用虚函数时,如果派生类重写了基类的虚函数,则调用的仍然是派生类的虚函数;如果派生类没有重写基类的虚函数,则调用的仍然是基类的虚函数。所以本质上来讲,c++的类型转换,并没有真正的转换,只是改变了指针能够访问内存的范围,而且该转换而来的基类指针可以访问基类的非虚函数。

        本章节后面的内容作者继续探讨了指针相关的话题。无论什么类型的指针,指针与指针之间的本质并没有什么不同,指针就是一个地址,在64位机器上是8个字节。指针类型的作用在于告诉编译器(cpu)如何解析一块内存,及要解析的内存有多长。多态即是基于指针的这种特点来实现的。派生类-基类的转换,就是改变了指针的类型,从而改变了对内存的解析。

以上就是Inside the c++ object model第一章的大致内容及自己的实践。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Inside the C++ Object Model focuses on the underlying mechanisms that support object-oriented programming within C++: constructor semantics, temporary generation, support for encapsulation, inheritance, and "the virtuals"--virtual functions and virtual inheritance. This book shows how your understanding the underlying implementation models can help you code more efficiently and with greater confidence. Lippman dispells the misinformation and myths about the overhead and complexity associated with C++, while pointing out areas in which costs and trade offs, sometimes hidden, do exist. He then explains how the various implementation models arose, points out areas in which they are likely to evolve, and why they are what they are. He covers the semantic implications of the C++ object model and how that model affects your programs. Highlights *Explores the program behavior implicit in the C++ Object Model's support of object-oriented programming. *Explains the basic implementation of the object-oriented features and the trade offs implicit in those features. *Examines the impact on performance in terms of program transformation.* Provides abundant program examples, diagrams, and performance measurements to relate object-oriented concepts to the underlying object model. If you are a C++ programmer who desires a fuller understanding of what is going on "under the hood," then Inside the C++ Object Model is for you! Get a value-added service! Try out all the examples from this book at www.codesaw.com. CodeSaw is a free online learning tool that allows you to experiment with live code from your book right in your browser.

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值