以下的讨论都是在不用#pragma pack( num )下的结果,num表示以多少个字节对齐。

初学者在学习面向对象的程序设计语言时,或多或少的都些疑问,我们写的代码与最终生编译成的代码却 大相径庭,我们并不知道编译器在后台做了什么工作.这些都是由于我们仅停留在语言层的原因,所谓语言层就是教会我们一些基本的语法法则,但不会告诉我们为什么这么做?今天和大家谈的一点感悟就是我在学习编程过程中的一点经验,是编译器这方面的一个具体功能.

首先:我们要知道什么是类的实例化,所谓类的实例化就是在内存中分配一块地址.

那我们先看看一个例子:

#include<iostream.h>

class a {};
class b{};
class c:public a{
virtual void fun()=0;
};
class d:public b,public c{};
int main()
{
cout<<"sizeof(a)"<<sizeof(a)<<endl;
cout<<"sizeof(b)"<<sizeof(b)<<endl;
cout<<"sizeof(c)"<<sizeof(c)<<endl;
cout<<"sizeof(d)"<<sizeof(d)<<endl;
return 0;}

程序执行的输出结果为:

sizeof(a) =1

sizeof(b)=1

sizeof(c)=4

sizeof(d)=8

上面是在VC++6.0编译的结果,但是在Dev-C++和Code::Blocks下得出的结果是 sizeof( d ) = 4

为什么会出现这种结果呢?初学者肯定会很烦恼是吗?类a,b明明是空类,它的大小应该为为0,为什么 编译器输出的结果为1呢?这就是我们刚才所说的实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.所以a,b的大小为1.

而类c是由类a派生而来,它里面有一个纯虚函数,由于有虚函数的原因,有一个指向虚函数的指针(vptr),在32位的系统分配给指针的大小为4个字节,所以最后得到c类的大小为4.

类d的大小更让初学者疑惑吧,类d是由类b,c派生迩来的,它的大小应该为二者之和5,为什么却是8  呢?这是因为为了提高实例在内存中的存取效率.类的大小往往被调整到系统的整数倍.并采取就近的法则,里哪个最近的倍数,就是该类的大小,所以类d的大小为8个字节.

当然在不同的编译器上得到的结果可能不同,但是这个实验告诉我们初学者,不管类是否为空类,均可被实例化(空类也可被实例化),每个被实例都有一个独一无二的地址.

我所用的编译器为vc++ 6.0.

下面我们再看一个例子.

#include<iostream.h>
class a{
pivate:
int data;
};

class b{
private:
     int data;
static int data1;
};
int b::data1=0;
void mian(){
cout<<"sizeof(a)="<<sizeof(a)<<endl;
cout<<"sizeof(b)="<<sizeof(b)<<endl;
}

执行结果为:

sizeof(a)=4;

sizeof(b)=4;

为什么类b多了一个数据成员,却大小和类a的大小相同呢?因为:类b的静态数据成员被编译器放在程序的一个global data members中,它是类的一个数据成员.但是它不影响类的大小,不管这个类实际产生 了多少实例,还是派生了多少新的类,静态成员数据在类中永远只有一个实体存在,而类的非静态数据成员只有被实例化的时候,他们才存在.但是类的静态数据成员一旦被声明,无论类是否被实例化,它都已存在.可以这么说,类的静态数据成员是一种特殊的全局变量.

所以a,b的大小相同.

下面我们看一个有构造函数,和析构函数的类的大小,它又是多大呢?

#include<iostream.h>
class A{
public :
A(int a){
   x=a;}
void f(int x){
   cout<<x<<endl;}
~A(){}

private:
   int x;
   int g;
   };
class B{
public:
private:
int data; int data2;
static int xs;
};
int B::xs=0;
void main(){
A s(10);
s.f(10);
cout<<"sozeof(a)"<<sizeof(A)<<endl;
cout<<"sizeof(b)"<<sizeof(B)<<endl;
}程序执行输出结果为:

10 ,

sizeof(a) 8

sizeof(b) 8

它们的结果均相同,可以看出类的大小与它当中的构造函数,析构函数,以及其他的成员函数无关,只与它当中的成员数据有关.

从以上的几个例子不难发现类的大小:

1.为类的非静态成员数据的类型大小之和.

2.有编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针).

3.为了优化存取效率,进行的边缘调整.

4 与类中的构造函数,析构函数以及其他的成员函数无关.

虚继承之单继承的内存布局

先看一段代码
class A
{
      virtual aa(){};
};

class B : public virtual  A
{
      char j[3];                                    //加入一个变量是为了看清楚class中的vfptr放在什么位置
      public:
            virtual bb(){};
};
class C : public virtual B
{
      char i[3];
      public:
            virtual cc(){};
};

这次先不给结果,先分析一下,也好加强一下印象。
1、对于class A,由于只有一个虚函数,那么必须得有一个对应的虚函数表,来记录对应的函数入口地址。同时在class A的内存空间中之需要有个vfptr_A指向该表。sizeof(A)也很容易确定,为4。
2、对于class B,由于class B虚基础了class A,同时还拥有自己的虚函数。那么class B中首先拥有一个vfptr_B,指向自己的虚函数表。还有char j[3],做一次alignment,一般大小为4。可虚继承该如何实现咧?首先要通过加入一个虚l类指针(记vbptr_B_A)来指向其父类,然后还要包含父类的所有内容。有些复杂了,不过还不难想象。sizeof(B)= 4+4+4+4=16(vfptr_B、char j[3]做alignment、vbptr_B_A和class A)。
3、在接着是class C了。class C首先也得有个vfptr_C,然后是char i[3],然后是vbptr_C_B,然后是class B,所以sizeof(C)=4+4+4+16=28(vfptr_C、char i[3]做alignment、vbptr_C_A和class B)。

在VC 6.0下写了个程序,把上面几个类的大小打印出来,果然结果为4、16、28。

VC中虚继承的内存布局——单继承
画了个图,简单表示一下我跟踪后的结果


虚基础之单继承时的内存布局图

class A的情况太简单,没问题。从class B的内存布局图可以得出下面的结论。
1、vf_ptr B放在了类的首部,那么如果要想直接拿memcpy完成类的复制是很危险的,用struct也是不行的。

2、vbtbl_ptr_B,为什么不是先前我描述的vbptr_B_A呢?因为这个指针与我先前猜测的内容有很大区别。这个指针指向的是class B的虚类表。看看VB table,VB table有两项,第一项为FFFFFFFC,这一项的值可能没啥意义,可能是为了保证虚类表不为空吧。第二项为8,看起来像是class B中的class A相对该vbtbl_ptr_B的位移,也就是一个offset。类似的方法在C++ Object Model(P121)有介绍,可以去看看。
class C的内存布局就比较复杂了,不过它的内存布局也更一步说明我对vbtbl_ptr_B中的内容,也就是虚类表的理解是正确的。不过值得关注的是class B中的class A在布局时被移到前面去了,虽然整个大小没变,但这样一来如果做这样的操作  C c; B *b;b=&c;时b的操作如何呢?此时只要从c的虚类表里获得class B的位置既可赋值给b。但是在构建class C时会复杂一些,后面的使用还是非常简单的,效率也比较高。class A的内存布局被前移可能是考虑倒C的虚继承顺序吧。

结论
1、VC在编译时会把vfptr放到类的头部;
2、VC采用虚表指针(vbtbl_ptr)来确定某个类所继承的虚类。
3、VC会重新调整虚继承的父类在子类中内存布局。(具体规则还不清楚)
4、VC中虚类表中的第一项是无意义的,可能是为了保证sizeof(虚类表)!=0;后面的内容为父类在子类中相对该虚类表指针的偏移量。

代码

 1 #include <iostream>
 2 #include <cstdio>
 3 using namespace std;
 4 
 5 class Base {
 6 public:
 7     Base(){};
 8     virtual ~Base(){};
 9     void set_num( int num ) {
10         a = num;
11     }
12     virtual int get_num() {
13         return a;
14     }
15 private:
16     int a;
17     char *p;
18 };
19 
20 class Derive : public Base {
21 public:
22     Derive() : Base(){};
23     ~Derive(){};
24     virtual int get_num() {
25         return d;
26     }
27 private:
28     static int st;
29     int d;
30     char *p;
31     char c;
32 };
33 
34 int main()
35 {
36     cout << sizeof( Base ) << endl;
37     cout << sizeof( Derive ) << endl;
38 
39     return 0;
40 }

在Base类里添加了virtual int get_num()函数,而子类也重新实现了virtual int get_num()函数。

但是结果是

12

24

说明子类只是共用父类的虚函数表,因此一旦父类里有虚函数,子类的虚函数将不计入sizeof大小。

一个基类和一个派生类这个抽象的类概念里是各一张虚表。 但是在一个具体的类的实例里,比如派生类对象,其只有一个虚表入口地址,且指向派生类这个抽象的类概念的那个虚表。

转载于:https://www.cnblogs.com/zhangjing0502/archive/2012/07/23/2605055.html

C++中类的大小问题相关推荐

  1. c语言格式对齐填充_C ++中类的大小 课堂上的填充和对齐| 派生类的大小

    c语言格式对齐填充 Prerequisite: 先决条件: sizeof() operator in C/C++ C / C ++中的sizeof()运算符 Size of struct in C C ...

  2. C++学习笔记问题大全【整理】

    声明:以下大多问题都是网上找的,有部分答案根据自己个人的理解作出了修改,仅供参考 _______________ 整理笔记: 关于C++语言概述 如基本符号.词汇.基本框架 问: 面向对象的程序设计思 ...

  3. C++中的结构体与类

    C++中继承了C语言中的结构体,但同时也在C语言的基础上新增了一个类(class)的概念,类可以说是C语言中结构体的升级版,这里主要细讲C++中的类 1.C++结构体 2.C++类 1.c语言结构体: ...

  4. VC API常用函数

    第一个:FindWindow根据窗口类名或窗口标题名来获得窗口的句柄,该函数返回窗口的句柄, 这个函数的定义是这样的 HWND WINAPI FindWindow(LPCSTR lpClassName ...

  5. 脱了马甲我也认识你: 聊聊 Android 中类的真实形态

    [这是 ZY 第 19 篇原创技术文章] 我们在平时开发过程中,一定定义过无数个千奇百怪的类,但是大家有想过,一个 Java 文件中的 Class,在虚拟机中的真实形态是什么么? 这篇文章就带大家探讨 ...

  6. 【C++面向对象】类的大小以及虚继承

    先看下面一段代码: #include <iostream>using namespace std;class X{}; class Y:public virtual X{}; class ...

  7. java JLabel改变大小后如何刷新_到底一行java代码是如何在计算机上执行的

    不知道你是否思考过,每次我们在IDEA中右键Run Application启动主方法,假如程序运行正常,控制台也打印出了你所要打印的信息,在这个过程中你知道这台计算机上那些硬件及其软件都是以什么样的方 ...

  8. python字典元素的值是否相等_对Python中等值和大小比较

    等值.大小比较 在python中,只要两个对象的类型相同,且它们是内置类型(字典除外),那么这两个对象就能进行比较.关键词:内置类型.同类型.所以,两个对象如果类型不同,就没法比较,比如数值类型的数值 ...

  9. C++中类(class)和结构(struct)的区别

    类描述看上去很像包含成员函数以及public和private可见性标签的结构声明,实际上,C++对结构进行了扩展,使之具有与类相同的特性.它们之间的唯一区别是:结构的默认访问类型是public,而类的 ...

最新文章

  1. hdu 2441(ACM(Array Complicated Manipulation))
  2. C# VS2012操作word文档 (二).插入表格图片
  3. Mybatis实体类属性名与数据库类名不对应的两种解决方法
  4. linux libasan.so,Address Sanitizer 用法
  5. 交换两个数组 差最小 java_如何交换两个等长整形数组使其数组和的差最小(C和java实现)...
  6. 正常使用 flex profiler 解决 Socket timeout
  7. 邻接表的建立与spfa算法
  8. UVA10523 Very Easy !!!题解
  9. 为什么我们要设定更高的目标?
  10. AngularJS的基础元素应用
  11. Python移植MATLAB的巴特沃斯滤波器,椭圆滤波器
  12. 安全生产计算机考试成绩查询,安全生产在线模拟考试成绩查询
  13. 体现SRP(单一职责原则)的两种模式——工厂模式和命令模式
  14. Redis zset的zadd()和zincrby()踩坑记录
  15. 高精度1------高精度乘法
  16. IDEA插件系列(19):EduTools插件——学习编程语言
  17. 超级网管员——网络基础
  18. 使用小米手机NFC功能充值交通卡
  19. 联想服务器在UEFI HII界面中配置阵列的基本操作
  20. Centos8常用软件安装

热门文章

  1. Springboot启动原理解析
  2. 支付宝架构师眼中的高并发架构
  3. 用机器学习神器sklearn做特征工程!
  4. Transformers资料汇总!从原理到应用
  5. 人脑是怎么防止梯度消失和梯度爆炸的?
  6. GAN版马里奥创作家来了:一个样本即可训练,生成关卡要素丰富 | 开源
  7. 清华特奖答辩前10出炉,两名CS学生3篇顶会一作,有人周读3000页英文论文!
  8. 算法科普:神秘的 DES 加密算法
  9. 列表标签ul、ol、dl、li
  10. TCP流量控制-TCP拥塞控制 拥塞控制与流量控制的区别