在C++中经常会遇到一个类的大小问题,关于一个类的大小一般受到三个方面的影响。

  1. 语言本身所造成的额外负担,如在虚拟继承中会遇到如派生类中会包含一个指针指向base class subobjec,这样会造成内存的额外负担。
  2. 编译器对于特殊情况所提供的的优化。如果编译器不提供优化,则会在一个空的基类中会形成至少一个char类型的内存空间,传统上这一个char类型的空间会被放在该类内存的固定尾部,但是有些编译器会提供优化的策略,在空类型中不会生成这一个char成员对象的空间。
  3. Alignment的限制。如果一个中含有虚函数,其中如果包含一个char类型的函数成员,则会发生内存空间对其的情况,从而出现sizeof class_name = 8的情况,相当于struct中的内存对齐。

3.1 Data Member的绑定

在类中如果有一个函数成员的名称和一个全局变量的名称相同,在类内的某些函数对其进行参阅(取用)操作,相信很多人会说该操作是对类内成员的操作,对于今天的编译器来说答案是正确,但是在很早的时候,这个操作是针对的global x object的操作。当然对于这种在当时的情况下出现了两种防御措施。

  1. 把所有的数据成员放在类的声明起头处,以确保正确的绑定:
  1. class X{
  2. float x, y, z;
  3.  
  4. public:
  5. float get_X(){return x}
  6. }

  2. 把所有的内联函数不管大小都放在类声明之外。

  1. class X{
  2. float x, y, z;
  3.  
  4. public:
  5. float get_X(){}
  6. }
  7.  
  8. inline float get_X(){
  9. return x;
  10. }

  上面的情况在C++2.0之后就已经消失了,但是关于成员函数的形参中关于类型的操作应该加以注意如:

  1. typedef int length;
  2.  
  3. class Point3d{
  4. void numble(length val){_val = val}//此处的length为int
  5. length numble(){return _val} //此处的length为int
  6. private:
  7. typedef length float;
  8. length _val;
  9. };

在这种情况下以上两种防御型风格仍然需要。

3.2 Data Member的布局

非静态成员的顺序和声明的顺序一样(同一访问权限中(public, protected, private)较晚出现的成员在类对象的较高的地址处),静态成员不会放在一个对象的布局之中。

3.3 Data Member的存取

1. 静态成员

class_name::static_data_member;

class_obj.static_data_member;

2. 非静态成员

  1. Point3d Point3d::translate(const Point3d &pt){
  2. x += pt.x;
  3. y += pt.y;
  4. z += pt.z;
  5. }
  6.  
  7. 将被转化为
  8.  
  9. Point3d Point3d::translate(const Point3d &pt){
  10. this->x += pt.x;
  11. this->y += pt.y;
  12. this->z += pt.z;
  13. }
  14.  
  15. 关于取地址操作
    origin._y = 0;
    &origin._y的操作为&origin + (&origin._y - 1

在类内声明的变量只会按照声明顺序来进行安排其内存地址如:

  1. #include<stdio.h>
  2. //#include<string.h>
  3. #include<string>
  4. #include<windows.h>
  5. class A;
  6. void p_printf();
  7.  
  8. void print(char s[], int *b){
  9. char szbuffer[] = {};
  10. wsprintf(szbuffer, s, b);
  11. printf(szbuffer);
  12. }
  13.  
  14. class A{
  15.  
  16. void friend print(char, A *);
  17. void friend p_print();
  18. public:
  19. int public_A;
  20. protected:
  21. int protected_A;
  22. private:
  23. int private_A;
  24.  
  25. public:
  26. int public_B;
  27. protected:
  28. int protected_B;
  29. private:
  30. int private_B;
  31.  
  32. };
  33.  
  34. void p_print(){
  35. A a;
  36. printf("the a address is %#x\n\0", &a);
  37. print("the a.public_A address is %#X\n\0", &a.public_A);
  38. print("the a.protected_A address is %#X\n\0", &a.protected_A);
  39. print("the a.private_A address is %#X\n\0", &a.private_A);
  40. print("the a.public_B address is %#X\n\0", &a.public_B);
  41. print("the a.protected_B address is %#X\n\0", &a.protected_B);
  42. print("the a.private_B address is %#X\n\0", &a.private_B);
  43.  
  44. }
  45.  
  46. int main(void){
  47.  
  48. p_print();
  49.  
  50. }
  51.  
  52. 输出为:
  53. the a address is 0x28fef8
  54. the a.public_A address is 0X28FEF8
  55. the a.protected_A address is 0X28FEFC
  56. the a.private_A address is 0X28FF00
  57. the a.public_B address is 0X28FF04
  58. the a.protected_B address is 0X28FF08
  59. the a.private_B address is 0X28FF0C
  60.  
  61. Process returned (0x0) execution time : 0.021 s
  62. Press any key to continue.

3.4 继承与数据成员

1. 只继承无多态

这是因为在进行向上转换的时候采用memberwise拷贝,如果不各自对其,当由基类向派生类拷贝时容易出现

2. 加上多态

多态作为C++的类的三大特性(封装,继承,多态),在内存中的布局为继承体系中每个类内存对其后的按照继承顺序叠加在一起,最后是该类自身的数据成员,由于含有虚函数,所以会产生一个虚函数表,至于虚函数指针(一个指向虚函数表的指针)(C++标准对虚函数指针的存放位置并没有严格规定,但是编译器一般情况是放在一个类的头部或者是尾部)。

3. 多重继承

如上所说,按照继承顺序进行叠加数据成员,每个类的自身虚函数指针仍然在自己类的相应部分。

4. 虚拟继承

在类的继承体系中,有的时候会出现某几个父类自于继承自同一个base class,如下图所示,为避免空间的浪费,采用虚拟继承

使得继承模型变为

但是在这种模型设计过程中发现会出现随着继承类的增加会增加指向虚拟类的指针内存负担,从而出现将这些指针转移至虚函数指针在-1处添加共享类的偏移地址,0处代表type_info for class, 正值为该类的virtual func的函数地址,如:

第3章 Data语意学的更多相关文章

  1. 第4章 Function语意学

    第4章 Function语意学 目录 第4章 Function语意学 4.1 Member的各种调用方式 Nonstatic Member Function(非静态成员函数) virtual Memb ...

  2. 第 7 章 Data 类型

    目录 第 7 章 Data 类型 一.创建方式 二.转时间戳 其他 第 7 章 Data 类型 @(es5) 参考了: 阮一峰javascript的标准.<javascript高级教程> ...

  3. 【C++对象模型】第六章 执行期语意学

    执行期语意学,即在程序执行时,编译器产生额外的指令调用,确保对象的构造,内存的释放,以及类型转换与临时对象的生成的安全进行. 1.对象的构造和析构 对于类对象的构造,一般在定义之后则开始内部的构造过程 ...

  4. 《深度探索C++对象模型》第二章 | 构造函数语意学

    默认构造函数的构建操作 默认构造函数在需要的时候被编译器合成出来.这里"在需要的时候"指的是编译器需要的时候. 带有默认构造函数的成员对象 如果一个类没有任何构造函数,但是它包含一 ...

  5. 《深度探索c++对象模型》chapter3 Data语意学

    一个空的class:如 class X{} ; sizeof(X)==1; sizeof为什么为1,他有一个隐晦的1 byte,那是被编译器安插进去的一个char,这使得class2的两个object ...

  6. data语意学

    引例: class X{}; class Y:public virtual X{}; class Z:public virtual X{}; class A:public Y,public Z{}; ...

  7. c++ data语意学

     Data Member的绑定 extern float x; class Point3d { public: point3d(); //问题:被传回和被设定的x是哪一个x呢? float X() c ...

  8. 《深度探索C++对象模型》笔记——Data语意学

    Data Member的绑定 inline member functin躯体之内的一个data member绑定操作会在整个class声明完成之后才发生. argument list中的名称还是会在它 ...

  9. 【C++】深度探索C++对象模型读书笔记--Data语意学(The Semantics of data)

    1. 一个空类的大小是1 byte.这是为了让这一类的两个对象得以在内存中配置独一无二的地址. 2. Nonstatic data member 放置的是“个别的class object”感兴趣的数据 ...

随机推荐

  1. 洛谷P2886 [USACO07NOV]牛继电器Cow Relays

    题意很简单,给一张图,把基本的求起点到终点最短路改成求经过k条边的最短路. 求最短路常用的算法是dijkstra,SPFA,还有floyd. 考虑floyd的过程: c[i][j]=min(c[i][ ...

  2. 关于C#mvc用iis发布,虚拟目录的问题。

    mvc关于iis发布虚拟目录的问题,解决方法是修改代码中路径的方式,例如ajax中常用的为url:“/Home/Index”,可修改为 url: '@Url.Action("Index&qu ...

  3. 算法面试题(python)——如何找出数组中出现一次的数

    题目描述: 一个数组里,除了三个数是唯一出现的,其余的数都出现了偶数次,找出这三个数中任意一个.比如数组序列为[1,2,4,5,6,4,2],只有1.5.6这三个数字是唯一出现的,数字2.4均出现了偶 ...

  4. sticky

    最近有点忘了position几个取值的内容,在这里简单总结一下. position的含义是指定位类型,取值类型可以有:static.relative.absolute.fixed.inherit和st ...

  5. 集合练习 练习:每一个学生Student都有一个对应的归属地定义为String类型。学生属性:姓名,年龄 注意:姓名和年龄相同的视为同一个学生。保证学生的唯一性。 1、描述学生。 2、定义Map容器,将学生作为键,地址作为值存入集合中。 3、获取Map中的元素并进行排序。

    package com.rf.xs.map; public class Student implements Comparable<Student> { private String na ...

  6. CSS3简单画出3d图形

    1.气球 2.泳圈 1.2两图实现代码分别如下: <html> <head> <meta charset="utf-8"> <meta h ...

  7. java xml 转 json

    <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> ...

  8. C# 8.0 抢先看-- Async Stream

    异步流? Async Stream 简单说来是一种非同步的迭代器模式,说更白一点就是可以await 的foreach.在过去的C# 中如果要回传一个可迭代的IEnumerable<T> , ...

  9. Oracle数据库各种名字的区别

    数据库名(DB_NAME).数据库实例名(INSTANCE_NAME).操作系统环境变量(ORACLE_SID).数据库服务名(SERVICE_NAME).数据库域名(DB_DOMAIN)以及全局数据 ...

  10. DAY:3 列表

    列表元素提取 mes = ["北京","哈尔滨","长春","沈阳","天津"] print(mes ...