编译环境:Windows10 + VS2015

1、空类占用的内存空间

类占内存空间是只类实例化后占用内存空间的大小,类本身是不会占内存空间的。用sizeof计算类的大小时,实际上是计算该类实例化后对象的大小。空类占用1字节原因:C++要求每个实例在内存中都有一个唯一地址,为了达到这个目的,编译器会给空类隐含添加1字节,保证空类实例化后在内存中得到的地址是独一无二的。所以如下代码中sizeof(A)(实际上是用类A的默认构造函数构造A的临时对象,然后求这个临时对象的大小),sizeof(obj)大小都是1字节。

class A
{
}; int main()
{
int nLen = sizeof(A);
std::cout<<nLen<<std::endl; //输出为1
A obj;
nLen = sizeof(obj);
std::cout << nLen << std::endl; //输出为1
return 0;
}

我们可以在快速监视对话框中找到obj的内存地址。

在内存窗口中找到该内存地址0x012ff8c7,说明内存中确实给空类的实例化对象分配了1字节内存空间。

2、类成员函数不占内存空间

类的普通成员函数不占内存空间。下列代码中A类添加了3个成员函数,此时类A 实例化对象obj大小仍然为1字节。原因是:成员函数还是以一般函数一样的存在,obj.fun1()是通过fun1(obj.this)来调用的。所谓成员函数名义上是类中的,其实成员函数的大小不再类对象里面,同一个类的多个对象共享函数代码。而我们访问类的成员函数是通过类的指针实现,而这个指针指向的是一个table,table里面记录了各个成员函数的地址(不同编译器可能有不同的实现)。所以,访问成员函数是间接获得地址的,这样也增加了一定的时间开销,这也是为什么提倡不一些简短的、调用频率高的函数声明为inline形式(内联函数)。

同理,类中构造函数和析构函数不占内存空间。

class A
{
public:
void fun1() {}
int fun2() { return 0; }
long long fun3() { return 0l; }
}; int main()
{
A obj;
int nLen = sizeof(obj);
std::cout << nLen << std::endl; //输出为1
return 0;
}

3、虚函数占用内存空间

C++类有虚函数的时候,会产生一个指向虚函数表的指针(vptr),在32位系统分配指针大小为4字节。而类中只要有虚函数,这个类就会产生一个指向虚函数的指针,有两个虚函数则产生两个指向虚函数的指针。而类中的指向虚函数的指针(一个或多个)要有一个地方存放,这些指针放在一个表格里(称为虚函数表),这个虚函数表保存在可执行文件中,在程序执行时载入到内存中。不管类中多多少个虚函数,其指向虚函数表的指针只有一个,占4个字节。注意一般从成员函数没有这个指针,因此不占类的内存空间。

1)有虚函数类产生一个指向虚函数表指针,占4字节。

class A
{
public:
virtual void fun1() {}
virtual void fun2() {}
}; int main()
{
A obj;
int nLen = sizeof(obj);
std::cout << nLen << std::endl; //输出为4
return 0;
}

内存分布如下图所示,类的地址0x006ffe88存放的是指向类虚函数表的指针地址0x00886b34。

2)父类与子类共享虚函数表指针,且子类的大小是其本身成员变量大小加上父类大小(在c++标准中,如果基类没有成员时,基类不应占空间,所以标准允许派生类的第一个成员与基类共享地址,此时基类不占据任何实际空间)。

class A
{
public:
virtual void fun1() {}
virtual void fun2() {}
};
class B :public A {
public:
virtual void fun3() {}
virtual void fun4() {}
};
int main()
{
B obj;
int nLen = sizeof(obj);
std::cout << nLen << std::endl; //输出为4
return 0;
}

内存分布如下:

通过VS2015可以查看到B对应的虚函数表。命令如下:

cl /d1 reportSingleClassLayoutB D:\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.cpp

显示如下:

3)虚函数表指针与类的成员变量一样参与类的字节对齐。所以如下代码中sizeof(obj)占8字节。

class A
{
public:
virtual void fun1() {}
virtual void fun2() {} };
class B
{
private:
char c;
};
class C:public A,public B
{};
int main()
{
C obj; int nLen = sizeof(obj);
std::cout << nLen << std::endl; //输出为8
return 0;
}

4、成员变量占内存空间

用例一

在A类中加入字符型变量a,可以看到类A对象obj的大小是1字节。

class A
{
public:
void fun1() {}
int fun2() { return 0; }
long long fun3() { return 0l; }
char a;
}; int main()
{
A obj;
int nLen = sizeof(obj);
std::cout << nLen << std::endl; //输出为1
obj.a = 'b';
return 0;
}

在快速监视对话框中找到obj的内存地址为0x00aff93f。此时该内存地址并未存储有效值。

执行obj.a='b'语句后,原来obj地址0x00aff93f存储上了十六进制数62,而十六进制62刚刚是字符b的ASCII码值。刚好1字节大小,跟sizeof(obj)输出吻合,同时也说明成员变量占用类对象空间。

用例二

在A类中变量a修改为整型型,可以看到类A对象obj的大小是4字节(刚好为sizeof(int)大小)。

class A
{
public:
void fun1() {}
int fun2() { return 0; }
long long fun3() { return 0l; }
int a;
}; int main()
{
A obj;
int nLen = sizeof(obj);
std::cout << nLen << std::endl; //输出为4
obj.a = 'b';
return 0;
}

开启调试,在内存中可以看到obj地址为0x009cf758。此时地址0x009cf758并未存储值。

执行obj.a = 'b'赋值语句后,在0x009cf758地址处存储了十六进制数62,刚好是字符b的ASCII码值。

用例三

在A类中加入字符变量a,整型b。可以看到类A对象obj的大小是8字节(为什么为8个字节呢?这涉及到类的字节对齐问题)。

class A
{
public:
void fun1() {}
int fun2() { return 0; }
long long fun3() { return 0l; }
char a;
int b; }; int main()
{
A obj;
int nLen = sizeof(obj);
std::cout << nLen << std::endl; //输出为8
obj.a = 'a';
obj.b = 'b';
return 0;
}

开启调试,在内存中可以看到obj占用的内存地址范围为0x001cfae8~0x001cfaef共8个字节内存空间。

执行obj.a = 'a';obj.b= 'b';赋值语句后在地址0x001cfae8处存储了十六进制数61,地址0x001cfaec出存储了十六进制数62。刚好为a,b的ASCII码值。

5、static修饰的静态成员变量不占内存空间

static修饰的静态成员变量不占内存空间,原因是编译器将其放在全局变量区。

class A
{
public:
virtual void fun1() {}
virtual void fun2() {}
static int a;
};
int A::a = 62; //须在类外初始化
int main()
{
A obj;
A::a = 63;
int nLen = sizeof(obj);
std::cout << nLen << std::endl; //输出为4
return 0;
}

内存布局如下图:

C++ 类对象内存模型分析的更多相关文章

  1. Objective-C类成员变量深度剖析--oc对象内存模型

    目录 Non Fragile ivars 为什么Non Fragile ivars很关键 如何寻址类成员变量 真正的“如何寻址类成员变量” Non Fragile ivars布局调整 为什么Objec ...

  2. 对C++对象内存模型造成的影响(类/对象的大小)

    首先重新回顾一下关于类/对象大小的计算原则: 类大小计算遵循结构体对齐原则 第一个数据成员放在offset为0的位置 其它成员对齐至min(sizeof(member),#pragma pack(n) ...

  3. C++对象内存模型2 (虚函数,虚指针,虚函数表)

    从例子入手,考察如下带有虚函数的类的对象内存模型: class A { public: virtual void vfunc1(); virtual void vfunc2(); void func1 ...

  4. Swift 对象内存模型探究(一)

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/zIkB9KnAt1YPWGOOwyqY3Q 作者:王 ...

  5. Java对象内存模型

    2 Java对象内存模型 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header). 实例数据(Instance Data)和对齐填充(Padding). 在 JVM ...

  6. C++/C#中堆栈、对象内存模型、深浅拷贝、Array.Clone方法

    转载自:http://blog.csdn.net/jarvischu/article/details/6425534 目录 1.      C++/C#中对象内存模型................. ...

  7. C#的对象内存模型

    转载自:http://www.cnblogs.com/alana/archive/2012/07/05/2577893.html C#的对象内存模型: 一.栈内存和堆内存1.栈内存 由编译器自动分配和 ...

  8. C++对象内存模型1(堆栈模型)

    对象内存模型 一. 栈(Stack) VS. 堆(heap) 栈 由系统自动管理,以执行函数为单位 空间大小编译时确定(参数+局部变量) 函数执行时,系统自动分配一个stack 函数执行结束时,系统立 ...

  9. (转)c#对象内存模型

    对象内存模型 C#的对象内存模型写这篇博客的主要目的是为了加深自己的理解,如有不对的地方,请各位见谅. C#的对象内存模型: 一.栈内存和堆内存1.栈内存 由编译器自动分配和释放,主要用来保存一些局部 ...

随机推荐

  1. 1248 - Dice (III)

    1248 - Dice (III)   PDF (English) Statistics Forum Time Limit: 1 second(s) Memory Limit: 32 MB Given ...

  2. 【嵌入式】keil不识别野火高速dap的问题

    解决方法:https://www.firebbs.cn/thread-28093-1-1.html

  3. Regularizing Deep Networks with Semantic Data Augmentation

    目录 概 主要内容 代码 Wang Y., Huang G., Song S., Pan X., Xia Y. and Wu C. Regularizing Deep Networks with Se ...

  4. uniapp中scroll-view自定义滚动条

    注意事项 需在app.vue中添加如下,需要important /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/ ::-webkit-scrollbar { width: 16upx!imp ...

  5. 编写Java程序,在子类老虎中重写父类动物的吃食方法

    返回本章节 返回作业目录 需求说明: 在子类老虎中重写父类动物的吃食方法 实现思路: 在子类老虎中重写父类动物的吃食方法的实现思路如下: 创建各种动物的父类Animal类,在该类中定义eat()方法. ...

  6. 编写Java程序,创建一个父类交通工具类(Vehicles),以及两个子类,分别是轿车类(Car)和卡车类(Truck)。

    返回本章节 返回作业目录 需求说明: 创建一个父类交通工具类(Vehicles),以及两个子类,分别是轿车类(Car)和卡车类(Truck). 父类有属性品牌(brand)和颜色(color). 在父 ...

  7. 论文翻译:2020_Attention Wave-U-Net for Acoustic Echo Cancellation

    论文地址:http://www.interspeech2020.org/uploadfile/pdf/Thu-1-10-10.pdf Attention Wave-U-Net 的回声消除 摘要 提出了 ...

  8. InnoDB学习(八)之 聚簇索引

    InnoDB中,表数据文件本身就是以主键为索引的B+树,树的叶子节点存放一条条表数据,此索引树被称为表的聚簇索引.聚簇索引也称为聚集索引,聚类索引,簇集索引,聚簇索引确定表中数据的物理顺序. Inno ...

  9. Linux-saltstack-1 saltstack的安装与基本配置

    @ 目录 一.环境介绍 二.安装saltstack 1.配置安装yum源 2.安装salt-master 3.安装slat-minion 三.配置salt-master 1.指定master的地址 2 ...

  10. Android系统编程入门系列之硬件交互——通信硬件USB

    在硬件交互的首篇对设备硬件的分类中,互联通信系列硬件主要用来与其他设备进行数据交互.从本文开始,将重点介绍该系列相关硬件. 互联通信系列硬件 根据硬件的可通信距离,由近及远分为USB.NFC.蓝牙.W ...