【C++】 类的对象占用内存大小及存储位置

1 类的对象占用内存大小

很多C++书籍中都介绍过,一个Class对象需要占用多大的内存空间。最权威的结论是:

1. 非静态成员变量总和。

2. 加上编译器为了CPU计算,作出的数据对齐处理。

3. 加上为了支持虚函数,产生的额外负担。

参考文章

(注:以下所有结果都是在VC6.0 开发环境中得出的结论)

1.1 空类的Size

class Car
{
};

void main()
{
       int size = 0;
       Car objCar;
       size = sizeof(objCar);
	   printf("%s %d /r", "Class Car Size:", size);
}

输出结果:Class Car Size:1

这是为何呢?
编译器在执行Car objCar;这行代码后需要,作出一个Class Car的Object。并且这个Object的地址还是独一无二的,于是编译器就会给空类创建一个隐含的一个字节的空间。

1.2 只有成员变量的Size

class Car
{
private:
       int nLength;
       int nWidth;
};
void main()
{
       int size = 0;
       Car objCar;
       size = sizeof(objCar);
       printf("%s %d /r", "Class Car Size:", size);
}

输出结果:Class Car Size:8

这个结果很多开发人员都清楚。在32位系统中,整型变量占4个字节。这里Class Car中含有两个整型类型的成员变量,所以Class Size是8。

class Car
{
private:
       int nLength;
       int nWidth;
       static int sHigh;
};

void main()
{
       int size = 0;
       Car objCar;
       size = sizeof(objCar);
       printf("%s %d /r", "Class Car Size:", size);
}

输出结果:Class Car Size:8

我们这次在Class Car中添加了一个静态成员变量,但是Class Size仍然是8个字节。这正好符合了,结论中的第一条:非静态成员变量总合。

class Car
{
private:
       char chLogo
       int nLength;
       int nWidth;
       static int sHigh;
};

void main()
{
       int size = 0;
       Car objCar;
       size = sizeof(objCar);
       printf("%s %d /r", "Class Car Size:", size);
}

输出结果:Class Car Size:12

在类中又插入了一个字符型变量,结果Class Size变成了12。这个就是编译器额外添加3个字符变量,做数据对齐处理,为了是提高CPU的计算速度。编译器额外添加的东西我们是无法看见的。这也符合了结论中的第二条:加上编译器为了CPU计算,作出的数据对齐处理。

既然,我们这样定义类成员数据编译器会额外的增加空。那么,我们何不在定义类的时候就考虑到数据对齐的问题,可以多定义出3个字符类型变量作为预留变量,既能满足数据对齐的要求,也给自己的程序添加了一些可扩展的空间。

1.3 只有成员函数的Size

class Car
{
public:
       Car(){};
       ~Car(){};
public:
       void Fun(){};
};

void main()
{
       int size = 0;
       Car objCar;
       size = sizeof(objCar);
       printf("%s %d /r", "Class Car Size:", size);
}

输出结果:Class Car Size:1

噢,这是怎么回事儿呢?再做一个实验看看。

class Car
{
public:
       Car(){};
       ~Car(){};
public:
       void Fun(){};
private:
       int nLength;
       int nWidth;
};

void main()
{
       int size = 0;
       Car objCar;
       size = sizeof(objCar);
       printf("%s %d /r", "Class Car Size:", size);
}

输出结果:Class Car Size:8

这次应该很清楚的了。函数是不占用类空间的。第一个例子中的Size为1个字节,正是编译器为类创建一个隐含的一个字节的空间

class Car
{
public:
       Car(){};
       virtual ~Car(){};
public:
       void Fun(){};
};

void main()
{
       int size = 0;
       Car objCar;
       size = sizeof(objCar);
       printf("%s %d /r", "Class Car Size:", size);
}

输出结果:Class Car Size:4

这次,让析构函数为虚函数,看到了Class Size为4。这正是指向Virtual Table的指针vptr的Size。这正好符合了,结论中的第三条:加上为了支持虚函数,产生的额外负担。

到此为止,一个Class Object究竟占用多少内存空间,已经完全说清楚了。但是,这只是针对单独类,或者说是基类适用。对于子类,却不一样了。有兴趣的朋友可以做一些实验。

1.4 类的对象成员函数不占用对象内存区

类的成员函数不占用对象内存区,函数的分布位置为.text段,同一个类实例化的所有对象共享相同的函数。

2 类的对象存储位置和对齐规则

通过几个测试场景来归纳一下类的成员变量存储的位置以及对齐规则
首先要知道每种类型的长度,以字节为单位:

Typesize
char1
short2
long4
int4
float4
fouble8

结论先放这里:
1. 以最长字节类型(此处的类型也包括类)为准,所有元素都要与最长字节类型长度对齐(即,整个类的对象长度一定为最长字节类型长度的整数倍);
2. 所有类型的起始地址都可以整除该类型字节长度。

以下测试硬件架构:
x86_64(AMD64)

2.1 test case 1

#include <iostream>

using namespace std;

class Constr_Mem
{
public:
    Constr_Mem();
    char a;
    char b;
    char c;
    char d;
};

Constr_Mem::Constr_Mem()
{
}

int main()
{
    Constr_Mem constr_mem;
    cout << "size of the instance: " << sizeof(constr_mem) << endl;
    cout << "the address of a: " << (void*)&constr_mem.a << endl;
    cout << "the address of b: " << (void*)&constr_mem.b << endl;
    cout << "the address of c: " << (void*)&constr_mem.c << endl;
    cout << "the address of d: " << (void*)&constr_mem.d << endl;
    return 0;
}

输出:

size of the instance: 4
the address of a: 0x7ffddbd57d50
the address of b: 0x7ffddbd57d51
the address of c: 0x7ffddbd57d52
the address of d: 0x7ffddbd57d53

2.2 test case 2

修改class Constr_Mem如下:

class Constr_Mem
{
public:
    Constr_Mem();
    int a;
    char b;
    char c;
    char d;
};

输出:

size of the instance: 8
the address of a: 0x7ffcd4960200
the address of b: 0x7ffcd4960204
the address of c: 0x7ffcd4960205
the address of d: 0x7ffcd4960206

2.3 test case 3

修改class Constr_Mem如下:

class Constr_Mem
{
public:
    Constr_Mem();
    char a;
    int b;
    char c;
    char d;
};

输出:

size of the instance: 12
the address of a: 0x7ffdc7993150
the address of b: 0x7ffdc7993154
the address of c: 0x7ffdc7993158
the address of d: 0x7ffdc7993159

2.4 test case 4

修改class Constr_Mem如下:

class Constr_Mem
{
public:
    Constr_Mem();
    char a;
    short b;
    char c;
    char d;
};

输出:

size of the instance: 6
the address of a: 0x7fff2ad1ec70
the address of b: 0x7fff2ad1ec72
the address of c: 0x7fff2ad1ec74
the address of d: 0x7fff2ad1ec75

2.5 test case 5

修改class Constr_Mem如下:

class Constr_Mem
{
public:
    Constr_Mem();
    char a;
    short b;
    short c;
    int d;
};

输出:

size of the instance: 12
the address of a: 0x7fff0cb91d90
the address of b: 0x7fff0cb91d92
the address of c: 0x7fff0cb91d94
the address of d: 0x7fff0cb91d98

2.6 结论

1. 以最长字节类型(此处的类型也包括类)为准,所有元素都要与最长字节类型长度对齐(即,整个类的对象长度一定为最长字节类型长度的整数倍);
2. 所有类型的起始地址都可以整除该类型字节长度。

3 进阶篇

此部分直接此参考文章

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值