C++类的大小计算汇总
C++中类涉及到虚函数成员、静态成员、虚继承、多继承、空类等。
类,作为一种类型定义,是没有大小可言的。
类的大小,指的是类的对象所占的大小。因此,用sizeof对一个类型名操作,得到的是具有该类型实体的大小。
- 类大小的计算,遵循结构体的对齐原则;
- 类的大小,与普通数据成员有关,与成员函数和静态成员无关。即普通成员函数、静态成员函数、静态数据成员、静态常量数据成员,均对类的大小无影响;
- 虚函数对类的大小有影响,是因为虚函数表指针带来的影响;
- 虚继承对类的大小有影响,是因为虚基表指针带来的影响;
- 静态数据成员之所以不计算在类的对象大小内,是因为类的静态数据成员被该类所有的对象所共享,并不属于具体哪个对象,静态数据成员定义在内存的全局区;
- 空类的大小(类的大小为1),以及含有虚函数,虚继承,多继承是特殊情况;
- 计算涉及到内置类型的大小,以下所述结果是在64位gcc编译器下得到(int大小为4,指针大小为8);
一、简单情况的计算
#include<iostream>
using namespace std; class base
{
public:
base()=default;
~base()=default;
private:
static int a;
int b;
char c; }; int main()
{
base obj;
cout<<sizeof(obj)<<endl;
}
计算结果:8(静态变量a不计算在对象的大小内,由于字节对齐,结果为4+4=8)。
二、空类的大小
C++的空类是指这个类不带任何数据,即类中没有非静态(non-static)数据成员变量,没有虚函数(virtual function),也没有虚基类(virtual base class)。
直观地看,空类对象不使用任何空间,因为没有任何隶属对象的数据需要存储。然而,C++标准规定,凡是一个独立的(非附属)对象都必须具有非零大小。换句话说,c++空类的大小不为0 。
#include <iostream>
using namespace std; class NoMembers
{
}; int main()
{
NoMembers n;
cout << sizeof(n) << endl;
}
计算结果1。
C++标准指出,不允许一个对象(当然包括类对象)的大小为0,不同的对象不能具有相同的地址。
这是由于:
- new需要分配不同的内存地址,不能分配内存大小为0的空间;
- 避免除以 sizeof(T)时得到除以0错误;
故使用一个字节来区分空类。
但是,有两种情况值得我们注意
第一种情况,空类的继承:
当派生类继承空类后,派生类如果有自己的数据成员,而空基类的一个字节并不会加到派生类中去。
class Empty {};
struct D : public Empty { int a;};
sizeof(D)为4。
第二种情况,一个类包含一个空类对象数据成员:
class Empty {};
class HoldsAnInt {
int x;
Empty e;
};
sizeof(HoldsAnInt)为8。
在这种情况下,空类的1字节是会被计算进去的。而又由于字节对齐的原则,所以结果为4+4=8。
继承空类的派生类,如果派生类也为空类,大小也都为1。
三、含有虚函数成员的类
虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。
每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就会为这个类创建一个虚函数表(VTABLE)保存该类所有虚函数的地址,其实这个VTABLE的作用就是保存自己类中所有虚函数的地址,可以把VTABLE形象地看成一个函数指针数组,这个数组的每个元素存放的就是虚函数的地址。在每个带有虚函数的类中,编译器秘密地置入一指针,称为vpointer(缩写为VPTR),指向这个对象的VTABLE。 当构造该派生类对象时,其成员VPTR被初始化指向该派生类的VTABLE。所以可以认为VTABLE是该类的所有对象共有的,在定义该类时被初始化;而VPTR则是每个类对象都有独立一份的,且在该类对象被构造时被初始化。
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
当定一个Base类的实例b时,其b中成员的存放如下:

指向虚函数表的指针在对象b的最前面。
虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符”\0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在Visual Studio下,这个值是NULL。而在linux下,如果这个值是1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。
因为对象b中多了一个指向虚函数表的指针,而指针的sizeof是8,因此含有虚函数的类或实例最后的sizeof是实际的数据成员的sizeof加8。
class Base {
public:
int a;
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
}
sizeof(Base)为16(vptr指针的大小为8,又因为对象中还包含一个int变量,字节对齐得8+8=16)。
四、基类含有虚函数的继承
(1)在派生类中不对基类的虚函数进行覆盖,同时派生类中还拥有自己的虚函数,比如有如下的派生类:
class Derived: public Base
{ public: virtual void f1() { cout << "Derived::f1" << endl; } virtual void g1() { cout << "Derived::g1" << endl; } virtual void h1() { cout << "Derived::h1" << endl; } };
基类和派生类的关系如下:

当定义一个Derived的对象d后,其成员的存放如下:

可以发现:
1)虚函数按照其声明顺序放于表中。
2)基类的虚函数在派生类的虚函数前面。
此时基类和派生类的sizeof都是数据成员的大小+指针的大小8。
(2)在派生类中对基类的虚函数进行覆盖,假设有如下的派生类:
class Derived: public Base
{ public: virtual void f() { cout << "Derived::f" << endl; } virtual void g1() { cout << "Derived::g1" << endl; } virtual void h1() { cout << "Derived::h1" << endl; } };
基类和派生类之间的关系:其中基类的虚函数f在派生类中被覆盖了。

当我们定义一个派生类对象d后,其d的成员存放为:

可以发现:
1)覆盖的f()函数被放到了虚表中原来基类虚函数的位置;
2)没有被覆盖的函数依旧;
3)派生类的大小仍是基类和派生类的非静态数据成员的大小+一个vptr指针的大小;
Base *b = new Derive();
b->f();
由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。
(3)多继承:无虚函数覆盖
假设基类和派生类之间有如下关系:

对于派生类实例中的虚函数表,是下面这个样子:

可以看到:
1) 每个基类都有自己的虚表;
2) 派生类的成员函数被放到了第一个基类的表中(所谓第一个基类是按照声明顺序来判断的);
由于每个基类都需要一个指针来指向其虚函数表,因此d的sizeof等于d的数据成员加上三个指针的大小。
(4)多重继承,含虚函数覆盖 :
假设,基类和派生类又如下关系:派生类中覆盖了基类的虚函数f 。

可以看见,三个基类虚函数表中的f()的位置被替换成了派生类的函数指针。这样,就可以任一静态类型的基类类来指向派生类,并调用派生类的f()了。
Derive d; Base1 *b1 = &d; Base2 *b2 = &d; Base3 *b3 = &d; b1->f(); //Derive::f() b2->f(); //Derive::f() b3->f(); //Derive::f() b1->g(); //Base1::g() b2->g(); //Base2::g() b3->g(); //Base3::g()
此情况派生类的大小也是类的所有非静态数据成员的大小+三个指针的大小。
#include<iostream>
using namespace std; class A
{
}; class B
{
char ch;
virtual void func0() { }
}; class C
{
char ch1;
char ch2;
virtual void func() { }
virtual void func1() { }
}; class D: public A, public C
{
int d;
virtual void func() { }
virtual void func1() { }
};
class E: public B, public C
{
int e;
virtual void func0() { }
virtual void func1() { }
}; int main(void)
{
cout<<"A="<<sizeof(A)<<endl; //result=1
cout<<"B="<<sizeof(B)<<endl; //result=16
cout<<"C="<<sizeof(C)<<endl; //result=16
cout<<"D="<<sizeof(D)<<endl; //result=16
cout<<"E="<<sizeof(E)<<endl; //result=32
return 0;
}
结果分析:
1.A为空类,所以大小为1 ;
2.B的大小为char数据成员大小+vptr指针大小。由于字节对齐,大小为8+8=16 ;
3.C的大小为两个char数据成员大小+vptr指针大小。由于字节对齐,大小为8+8=16 ;
4.D为多继承派生类,由于D有数据成员,所以继承空类A时,空类A的大小1字节并没有计入当中,D继承C,此情况D只需要一个vptr指针,所以大小为数据成员加一个指针大小。由于字节对齐,大小为8+8=16 ;
5.E为多继承派生类,此情况为我们上面所讲的多重继承,含虚函数覆盖的情况。此时大小计算为数据成员的大小+2个基类虚函数表指针大小 ,考虑字节对齐,继承顺序B在先,B(8 + 1),然后是C(8+1+1),由于字节对齐,B得与C中最大值对齐,因此B+7变成16,再+C(10),得26,最后+E的其它成员+1,因为要整体对于最大值(8)对齐,因此补齐得32。(之前看到几篇博客这里解释得都有问题)
四.虚继承的情况
对虚继承层次的对象的内存布局,在不同编译器实现有所区别。
在这里,只说一下在gcc编译器下,虚继承大小的计算。它在gcc下实现比较简单,不管是否虚继承,GCC都是将虚表指针在整个继承关系中共享的,不共享的是指向虚基类的指针。
class A {
int a;
virtual void myfuncA(){}
};
class B:virtual public A{
virtual void myfunB(){}
};
class C:virtual public A{
virtual void myfunC(){}
};
class D:public B,public C{
virtual void myfunD(){}
};
sizeof(A)=16,sizeof(B)=24,sizeof(C)=24,sizeof(D)=48;
A的大小为int大小加上虚表指针大小;
B,C中由于是虚继承,因此大小为int大小加指向虚基类的指针的大小。B,C虽然加入了自己的虚函数,但是虚表指针是和基类共享的,因此不会有自己的虚表指针,他们两个共用虚基类A的虚表指针。D由于B,C都是虚继承,其大小等于B+C)。
C++类的大小计算汇总的更多相关文章
- C++类的大小计算
转自http://www.tuicool.com/articles/uiUJry 一个空的class在内存中多少字节?如果加入一个成员函数后是多大?这个成员函数存储在内存中什么部分? 一个Class对 ...
- C++类所占内存大小计算
C++类所占内存大小计算 说明:笔者的操作系统是32位的. class A {}; sizeof( A ) = ? sizeof( A ) = 1明明是空类,为什么编译器说它是1呢? 空类同样可以实例 ...
- C++类对象大小的计算
(一)常规类大小计算 C++类对象计算需要考虑很多东西,如成员变量大小,内存对齐,是否有虚函数,是否有虚继承等.接下来,我将对此举例说明. 以下内存测试环境为Win7+VS2012,操作系统为32位 ...
- 关于虚拟继承类的大小问题探索,VC++ 和 G++ 结果是有区别的
昨天笔试遇到个 关于类占用的空间大小的问题,以前没怎么重视,回来做个试验,还真发现了问题,以后各位笔试考官门,出题时请注明是用什么编译器. vc6/vc8 cl 和 Dev-C 的g++ 来做的测试: ...
- sizeof求类的大小
用sizeof求类的大小,http://blog.csdn.net/szchtx/article/details/10254007(sizeof浅析(三)——求类的大小),这篇博文给出了非常详尽的举例 ...
- Java对象的内存布局以及对象所需内存大小计算详解
1. 内存布局 在HotSpot虚拟机中,对象的内存布局可以分为三部分:对象头(Header). 实例数据(Instance Data)和对齐填充(Padding). 1) 对象头(Header): ...
- C++学习笔记(8)----C++类的大小
C++类的大小 (i) 如下代码: #include<iostream> using namespace std; class CBase { }; class CDerive :publ ...
- Math类的数学计算功能
//Math类的数学计算功能 public class MathTest { public static void main(String[] args) { /*----------下面是三角运算- ...
- 类的大小——sizeof 的研究
类的大小——sizeof 的研究(1) 先看一个空的类占多少空间? class Base { public: Base(); ~Base(); }; 注意到我这里显示声明了构造跟析构,但是sizeof ...
随机推荐
- 16.vue-cli跨域,swiper,移动端项目
==解决跨域:== 1.后台 cors cnpm i -S cors 2.前端 jsonp 3.代理 webpack: myvue\config\index.js 找 proxyTable proxy ...
- ffmpeg快速获取视频截图
使用ffmpeg可以非常方便的生成视频截图,命令行下的mplayer也可以做视频截图,只不过mplayer在本质上还是调用ffmpeg来实现.ffmpeg 通过指定 -vcodec 参数为 mjpeg ...
- leetcode-Given a binary tree, find its minimum depth
第一题 Given a binary tree, find its minimum depth.The minimum depth is the number of nodes along the s ...
- Android无法删除项目+导入项目报错
Android无法删除项目+导入项目报错 Android无法删除项目:关闭eclipse或关闭电脑,然后重启,继续删除就可以了 导入项目报错:右键–>配置–>中就可以看到了,更改一下就可以 ...
- tcpdf开发文档(中文翻译版)
2017年5月3日15:06:15 这个是英文翻译版,我看过作者的文档其实不太友善或者不方便阅读,不如wiki方便 后面补充一些,结构性文档翻译 这是一部官方网站文档,剩余大部分都是开发的时候和网络总 ...
- [No0000186]治愈系课程教材 第一课
一部分:时态 时态有时间和特点组成 时间:现在.过去.将来 特点:一般.完成.进行.完成进行 所以时态总共有12种(加上过去将来的时间又多出4种时态,总共16种) 一般现在时 一般过去时 一般将来时 ...
- DELPHI中完成端口(IOCP)的简单分析(3)
DELPHI中完成端口(IOCP)的简单分析(3) fxh7622关注4人评论7366人阅读2007-01-17 11:18:24 最近太忙,所以没有机会来写IOCP的后续文章.今天好不容易有 ...
- darknet集成遇到的问题以及解决方法
将darknet集成进工程时,遇到了一些问题,下面记录一下解决方法: 集成步骤: 首先在yolo编译的时候,需要将三个开关打开: #define GPU#define CUDNN#define OPE ...
- 15.1-uC/OS-III资源管理(锁调度器)
1.大部分独占资源的方法都是创建临界段:1) 关中断方式2) 锁调度器方式3) 信号量方式4) mutex方式 2.独占共享资源的最快和最简单方法是关中断 然而,关/开中断是和CPU相关的操作,其相关 ...
- 保存退出vi编辑
保存命令按i进入编辑模式,编辑完成按ESC键 跳到命令模式,然后: :w 保存文件但不退出vi:w file 将修改另外保存到file中,不退出vi:w! 强制保存,不推出vi:wq 保存文件并退出v ...