`
anzhsoft
  • 浏览: 23347 次
  • 性别: Icon_minigender_1
  • 来自: 天津
文章分类
社区版块
存档分类
最新评论

C++对象模型(五):The Semantics of Data Data语义学

 
阅读更多

本文是《Inside the C++ Object Model》第三章的读书笔记。主要讨论C++ data member的内存布局。这里的data member 包含了class有虚函数时的vptr和vtable的布局情况。

1. 开头几个小问题

1. 首先回答一个问题: 一个空类,sizeof是多少?答案是1。因为编译器会生成一个隐晦的1bytes,用于区分,当该类多个对象时,各个对象都能在内存分配唯一地址。

2. 还有虚函数表的指针vptr,可能在类的开始,也可能在类的结尾。通常是类的结尾。(注:比较新的VC++和GCC都是在开头。不知道是否所有的版本都是)。

3. 关于成员变量的内存对齐,例如一个类只有char a一个属性; 但是它的大小是4(32位。64位机器是8?但是我是用GCC的sizeof仍然是1。熟悉汇编应该知道,这个地址应该不会存其他的内容了,因此说sizeof是4/8也可理解)。虽然char的大小是1。

4. 属性的内存顺序和声明顺序是一致的。不同级别(public、protected和private)属性的排列顺序是相对一致的,就是说可能不连续,但是必须符合较晚出现的属性存在较高的地址。

2. vptr值的不同存储方式

以下的图来自http://blog.csdn.net/hherima同学。非常感谢hherima同学的图。我将使用hherima同学的图,加上我自身的理解来彻底巩固并且分享给各位可爱的程序猿们。

下图演示单一继承并含有虚函数情况下的数据布局。Point2d 和Point3d是继承关系。Point2d含有虚函数,而Point3d自身没有虚函数。


注意:vptr放在类的末尾。这种方式在刚开始被很多编译器采用,因为可以保存base c struct的内存布局。

但是到了C++2.0,开始支持虚拟继承和抽象基类;并且由于OO的兴起,某些编译器开始把vptr放到class object的起头处。比如微软的第一个C++编译器就是采用这种方法。


前端存放的好处就是编译器可以直接访问虚函数表而不需要通过offset。当然代价就是与C的struct不再兼容。但是谁会从一个C struct派生出具有虚函数的C++ class呢?

如果是前端存放,还存在一个问题:如果基类没有虚函数,派生类有虚函数,那么单一继承的自然多态就会被打破。如果要将派生类转换成基类,必须编译器的介入。但是这种情况也比较少,因此多态就是为了继承,谁会设计出这种继承呢?既然这不是大多数的case,采用vptr在开头,那么就具有很好的意义。这种conventional实际上很利于编译器将C++编译到汇编,而且汇编也比较容易读。否则,放在结尾的话,每个class的data member数量是不一样的,因此vptr存储的offset也不一样。而放到头上,那么0号位置存的就是vptr,1号位置存的就是第一个data member,这样不单利于编译代码,也便于我们阅读反汇编的汇编代码。

3. 数据成员(data member)的内存布局

在上一小节中我们讨论了vptr的不同存放方式。编译器需要通过设置offset来存取vptr和data member。在98页关于对一个nonstatic data member的存取操作描述,feel confused:作者的意思是如果是直接取对象的第一个data member,那么需要在对象的地址+1。我不是太明白。如果是存取对象的第一个成员,那么对象的地址应该就是指向第一个成员的,它可能是vptr,也可能是第一个data member。那么如果是汇编,那么直接取该地址的内容,该地址的内容有可能是成员的值,也可能存的仍是地址(指针),那么offset+1没有意义。如果是C++的code,那么本身不需要这么麻烦,谁会直接将对象所在的地址进行解释,而不是通过C++的方式?当然某些高性能编程可能是,但是我实在想不出有任何理由要这样去做。

C++语言保证“出现在派生类中的基类对象,有其完整性”,这么做是为了在位拷贝的时候,能够拷贝正确。一般每个成员都会独占一个地址,意思是在32位机器上,每个数据成员至少占用4个B。当然为了内存对齐,比如有一下class:

class data{
  char a;
  char b;
  int c;
};

那么a和b可能会share一个地址单元,即sizeof(data) = 8;但是子类,父类的数据成员可以为了空间效率share一个地址单元吗?

假如Concrete1 和Concere2都有一个char的属性,而且Concere2继承自Concrete1。那么如果这两个数据成员share一个地址单元会有什么问题?那么我们思考一下以下的赋值能符合我们的预期吗?

Concrete1 *pc1_1, pc1_2;
Concrete2 c2;
pc1_1 = &c2;
//memory allocate for pc1_2
*pc1_2 = *pc1_1;

注意,从pc1_1到pc1_2的memberwise复制(复制一个一个的member)时,pc1_1的char b就被抹掉了。那么pc1_1就丢掉了派生类的信息。而这个复制很显然不是我们需要的!


这也是为什么C++语言保证“出现在派生类中的基类对象,有其完整性”!



3. 多重继承(Multiple Inheritance)

对于一个多重派生对象,将其地址指定给“最左端(也就是第一个)基类的指针”,情况和单一继承时相同,因为两者都指向相同的起始地址。需要付出的成本只是地址的指定操作而已,至于第二个或后继的base class的地址指定操作,则需要进行地址修改:加上或者减去介于中间base class大小。

下图展示了多继承的关系。涉及到4个类 Point2d、Point3d、Vertex和Vertex3d(p115)



下面展示了多重继承的对象模型。


注意,多继承的情况下,drived clas可能会有两个或两个以上虚函数表指针

请看下面的表达式:

Vertex3d   v3d;
Vertex*     pv;
Point2d*   p2d;
Point3d *  p3d;
那么这个操作 pv = &v3d 需要转换内部代码

pv = (Vertex*)(((char*)&v3d) + sizeof(Point3d))

那么如果pv是从另外一个Vertex3d的指针(比如是pv3d)拷贝过来呢?那么需要考虑空指针的情况。

pv = pv3d
     ?(Vertex*)(((char*)&v3d) + sizeof(Point3d))   
     :0;
下面这两个操作,只需要拷贝地址就行了。

p2d = &v3d;

p3d = &v3d;

以下引自陈皓先生的名著《C++ 对象的内存布局(上)》中多重继承。使用的是VC++和GCC3.4.4

使用图片表示是下面这个样子:

我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。
3) 内存布局中,其父类布局依次按声明顺序排列。
4) 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

4. 虚拟多继承情况

下图可以表现Vertex3d 的继承体系图。左为多重继承,右为虚拟多重继承。

各个class的定义如下:

class Point2d{
...
protect:
  float _x, _y;
};

class Vertex: public virtual Point2d{
...
protected:
  Vertex *next;
};

class Point3d: public virtual Point2d{
...
protected:
  float _z;
};

class Vertex3d: public Vertex, public Point3d{
...
protected:
  float mumble;
};

不论是Vertex还是Point3d都内含一个Point2d。然而在Vertex3d的对象布局中,我们只需要单一一份Point2d就好。如何使多重继承,那么Vertex3d对象中将有两个Point2d,那么对Point2d的引用可能会有歧义。所以引入虚拟继承。然而编译器要实现虚拟继承,实在是困难度颇高。虚拟继承的原则就是:让VertexPoint3d各自维护的Point2d 折叠成一个有Vertex3d维护的单一Point2d,并且还可以保存base class 和derived class的指针之间的多台指定操作。

如果一个class含有virtual base classsubobjects, 那么,该对象将被分割为两部分:一个不变局部和一个共享局部。不变局部中的数据,不管后继如何演化,总是拥有固定的offset,所以这部分数据可以直接存取。至于共享局部(即virtual base class),这一部分的数据,其位置会因为每次的派生操作而有变化,所以他们只能被间接存取。各家编译器实现技术之间的差异就是间接存取的方法不同。

如何存取class的共享局部呢?cfront编译器会在每一个derived class中安插一个指向virtual base class的指针,这样就可以间接存取。这样的实现模型会有下面两个主要缺点:

1.每一个对象必须针对其每一个virtual base class 背负一个额外的指针。

解决方法有:第一个,Microsoft编译器引入所谓的virtual base class table。每一个class object如果有一个或多个virtual base class,就会由编译器安插一个指针,指向virtual base class table。至于真正的virtual base class 指针,当然是被放在该表格中。

请看下面的虚拟继承对象模型,如图。


红框内即所谓的“共享局部”,其位置会因每次派生操作而有所变化。虚拟破坏了base class 的对象完整型,虚拟继承会在自己类中生成一个虚函数表指针。

第二个、在virtual function table 中放置virtual base class的offset(不是地址)。


这个方法的好处是,巧妙的利用了虚函数表的结构,使得drived class 能够节省一个指针的大小。上图中蓝色曲线是offset

2.由于虚拟继承串链的加长,导致间接存取层次的增加。例如:如果我们有三层虚拟衍化,我就需要三次间接存取(经由三个virtual base class指针)。

这个问题的解决方案有:拷贝所有的virtual base class 的指针到drived class中。这样就解决了存取时间的问题,虽然会有空间的开销。


参考资料:

1. http://blog.csdn.net/haoel/archive/2008/10/15/3081328.aspx

2. http://blog.csdn.net/hherima/article/details/8888539

分享到:
评论

相关推荐

    深度探索C++对象模型

    深度探索C++对象模型 第0章 导读(译者的话) 第1章 关于对象(Object Lessons) 加上封装后的布局成本(Layout Costs ...第2章 构造函数语意学(The Semantics of constructors) 2.1 Default Constructor的建构操作

    深度探索模C++对象模型PDF

    第3章 Data语意学(The Semantics of Data) 3.1 Data Member的绑定(The Binding of a Data Member) 3.2 Data Member的布局(Data Member Layout) 3.3 Data Member的存取 Static Data Members Nonstatic Data ...

    深度探索C++对象模型 超清版

    第3章 Data语意学(The Semantics of Data) 3.1 Data Member的绑定(The Binding of a Data Member) 3.2 Data Member的布局(Data Member Layout) 3.3 Data Member的存取 Static Data Members Nonstatic Data ...

    《深度探索C++对象模型》(Stanley B·Lippman[美] 著,侯捷 译)

    第3章 Data语意学(The Semantics of Data) 3.1 Data Member的绑定(The Binding of a Data Member) 3.2 Data Member的布局(Data Member Layout) 3.3 Data Member的存取 Static Data Members Nonstatic Data ...

    The SemanticWeb-Semantics and Big Data 10th

    The SemanticWeb-Semantics and Big Data 10th

    C++对象模型

    oriented programming within C++: constructor semantics, temporary generation, support for encapsulation, inheritance, and "the virtuals"-virtual functions and virtual inheritance. This book shows how ...

    the formal semantics of programming language: an introduction

    本书为英文原版,内容包括程序语言设计的基本原理,以及关于语言设计的形式化语义的描述理论基础,内容较为专业,却是一本非常优秀的关于形式化语义的教材!

    A tour of C++ 2nd

    In A Tour of C++, Second Edition, Bjarne Stroustrup, the creator of C++, describes what constitutes modern C++. This concise, self-contained guide covers most major language features and the major ...

    Handbook of Big Data Technologies

    3 Big Data and Semantics in the Internet of Things 4 Social Mining 5 Graph Mining 6 Big Stream Data Mining 7 Geo-Referenced Data Mining 8 Conclusion SCADA Systems in the Cloud 1 Introduction 2 ...

    Inside The C++ Object Model 英文版

    oriented programming within C++: constructor semantics, temporary generation, support for encapsulation, inheritance, and "the virtuals"--virtual functions and virtual inheritance. This book shows how...

    A Tour of C++, 2nd Edition

    Coverage begins with the basics, then ranges widely through more advanced topics, including many that are new in C++17, such as move semantics, uniform initialization, lambda expressions, improved ...

    Making C++ Objects Persistent: the Hidden Pointers

    Our solutions are elegant in that they do not require modifying the C11 compiler or the semantics of C11. We also discuss another problem that arises because C11 allows base class pointers to point to...

    The formal semantics of programing language

    activity of trying to define the meaning of program constructions precisely can reveal all kinds of subtleties of which it is important to be aware. This book introduces the mathematics, techniques ...

    PDF电子书《Data Exchange: Semantics and Query Answering》

    PDF电子书《Data Exchange: Semantics and Query Answering》

    Inside the C++ object model 高清英文版

    oriented programming within C++: constructor semantics, temporary generation, support for encapsulation, inheritance, and "the virtuals"-virtual functions and virtual inheritance. This book shows how ...

    Semantics of Asynchronous JavaScript

    Since the JavaScript language is single threaded, Node.js programs must make use of asynchronous callbacks and event loops managed by the runtime to ensure appli- cations remain responsive....

    Inside the C++ Object Model

    oriented programming within C++: constructor semantics, temporary generation, support for encapsulation, inheritance, and "the virtuals"-virtual functions and virtual inheritance. This book shows how ...

    Formal Syntax And Semantics of Programming Languages

    With this book, readers with a basic grounding in discreet mathematics will be able to understand the practical applications of these difficult concepts. The book presents the typically difficult ...

Global site tag (gtag.js) - Google Analytics