下方包含的代码运行环境为DEV C++ 5.11
字节对齐
struct A{
int a;//4
short b;//2+2
int c;//4
char d;//1+3
};
struct B{
int a; // 4
short b; // 2
char d;// 1 + 1
int c; // 4
};
sizeof(A) = 16, sizeof(B) = 12
需要字节对齐的根本原因在于CPU访问数据的效率问题。不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间
#pragma pack(n) // 指定内存对齐方式,往后的的代码什么的不按自身宽度对齐而是按指定宽度对齐;
#pragma pack() // 取消用户自定义字节对齐方式
#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
__attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
__attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
GCC默认按4字节对齐,
对于标准数据类型,地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
数组:按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
枚举:只占4个字节;
联合体:大于等于其成员中最宽的成员,且是其他成员变量基本类型的整数倍;
结构体:结构体中数据成员都要对齐,且结构体整体也需要对齐(整体对齐为数据成员最大基本类型的整数倍)
类:看下文;
内存占用
1、枚举eunm
对于enum变量,指一个被命名的整型常数的集合,本质上是一组(int型)常数,只占固定的 4 个字节;
2、联合体union
所有成员相对于基地址均为0,共享一段内存,并且同一时间只能储存其中一个成员变量的值(即后面的赋值将覆盖前面的赋值)空间需足够宽(大于等于其成员中最宽的成员)且大小能被其包含的所有基本数据类型的大小所整除。
// GCC 默认 4字节对齐
union test{
char s[9]; // 9
int a; // 4
double b; // 8
};
sizeof( test ); //>=最宽数据成员9, 且同时是其他类型char(1)、(int)4和double(8)的整数倍, 因此为16
union test{
char s[8]; // 8
int a; // 4
double b; // 8
};
printf("%d\n", sizeof( test ) ); // >=最宽数据成员8, 且同时是其他类型char(1)、(int)4和double(8)的整数倍, 因此为8
3、结构体
结构体各成员根据定义依次申请内存空间,其中包含数据成员自身对齐和结构体本身的对齐(整体对齐为数据成员最大基本类型的整数倍);
struct struct_test_1{
char a; // 1
int b; // 4
double c; // 8
}test1;
sizeof( test1 ); // 16
struct struct_test_2{
char a; // 1
double b; // 8
int c; // 4
static int d; // 存放在静态数据区,sizeof() 不计static所占用空间
}test2;
sizeof( test2 ) // 24
对struct_test_2分析:
首先char a 申请一个字节空间,开辟首地址,之后double b存入; 它会认为内存是以自己的大小(double = 8 byte)来划分,因此元素放置在自身宽度的整数倍开始(原则一),故b不从偏移1开始,因为不是整数倍,故中间填充7字节,此时消耗16字节,再开辟int c,4 字节,是从4字节的整数倍开始,故消耗了20字节。此时存储单元不是最宽元素(8bytes)的整数倍,故按最宽元素整数倍对齐(原则二);一共消耗24 字节。
struct Data
{
int a; // 4
float b; // 4
double c; // 8
char d[21]; // 21
}data;
printf("%d\n", sizeof(data) ); // 40 整体对齐为 数据成员最大基本类型(double)的整数倍
……………………………………………………………………………………………………………………………………………………………………………………
有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,
也就是用一个二进位。正是基于这种考虑,C语言又提供了一种叫做 位域 的数据结构。位域本质上是一种结构类型,不过其成员是按二进制位分配的。并且其类型必须是int型,这就包括有符号和无符号的。
在结构体或是联合体定义时,我们可以指定某个成员变量所占的用的二进制(bit)位数,这就是位域。位域的使用和结构体成员的使用相同,其一般形式为:
变量类型 位域变量名:位域 其中(位域变量名为可选项,)
struct bs{
int a:8;
int b:2;
int c:5;
};
printf("%d ", sizeof(struct bs) ); // 4
位域使用规则如下:
1、位域的宽度不能超过它所依附的数据类型的长度, 比如:unsigned int,长度为 4 个字节,数字就不能超过 32
2、位域可以无位域名,这时它只用来作填充或调整成员位置。因为没有名称,无名的位域是不能使用的。
位域存储规则如下:
1、依然遵循struct的 对齐规则;
2、当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。
struct bs{
int a:26;
int b:2;
int c:5;
};
printf("%d ", sizeof(struct bs) ); // 8
3、如果成员之间穿插着非位域成员,那么不会进行压缩。
struct bs{
int a:26;
int b;
int c:5;
};
printf("%d ", sizeof(struct bs) ); // 12
4、当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC 会压缩存储,而 VC/VS 不会。
举例说明
4、C++类(和struct类似,同样存在对齐问题)
a、空类占一个字节;
class A{
};
cout << "sizeof(A) "<< sizeof(A) << endl;// 1
b、数据成员所占内存按struct的计算方式计算(访问权限属性不影响存储,考虑到包含其他如struct、emun、union等复杂结构仍需先按自身类型对齐,在按class的算),其中static数据不是对象属性,而是类属性,其存储在数据区,sizeof 不计算其空间;
class A{
int a;
double c;
char b;
union{
int aa;
char bb[9];
double cc;
}test;
};
cout << "sizeof(A) "<< sizeof(A) << endl;// 40 = 4 + 4 + 8 + 1 + 7 + 16
c、不管是static还是非static成员函数,非虚成员函数均不占空间;
class B{
public:
void func1(){}
void func2(){}
};
cout << "sizeof(A) "<< sizeof(A) << endl;// 1
d、若存在虚函数,在类开辟的空间开始位置插入虚函数表(虚函数vptr指针),所有同一个类的虚函数共享一个虚函数表,故一共占用4个字节;
class B{
public:
void func1(){}
void func2(){}
virtual void func3() {}
virtual void func4() {}
};
cout << "sizeof(A) "<< sizeof(A) << endl;// 4
e、继承时,在内存中,派生类所在位置在基类前面;
非虚继承情况下,基类存在不存在虚函数,派生类会和基类均会共同使用一个虚函数表,
虚继承情况下,基类存在不存在虚函数,派生类的虚函数会共同使用派生类自己的虚函数表,
class B
{
private:
char ch;
virtual void func0() { }
};
class BB: public B
{
public:
int e;
virtual void func0() { }
virtual void func1() { }
};
class BB1: virtual public B
{
public:
int e;
virtual void func0() { }
virtual void func1() { }
};
cout<< "sizeof(B)" << sizeof(B) <<endl; // 8
// B的内存分布
// 4字节 虚表指针
// 1字节 ch
// 3字节 补齐,因为最大的字段(虚表长度)size为4,所以结构体补全成4的倍数
cout<< "sizeof(BB)"<< sizeof(BB) <<endl; // 12
cout<< "sizeof(BB1)"<< sizeof(BB1) <<endl; // 16
总结
总结1:结构体或联合体的数据成员对齐规则:
1、第一个数据成员放在ofset为0的地方,以后每个数据成员的对齐按照#pragmapack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、在数据成员完成各自对齐之后,结构或联合本身也要进行对齐,对齐将按照#pragmapack指定的数值和结构或联合最大数据成员长度中,比较小的那个进行。
总结2:C++类内存计算
1、基类对象的存储空间 = 非static数据成员大小 + 4字节虚函数表空间(若存在虚函数);
2、派生类对象大小 = 基类对象大小 + 派生类独有的非static数据成员大小(注意复杂结构的字节对齐) (普通继承,派生类会与基类共享虚函数表);
3、虚继承(在继承方式中加关键字virtual)的存储空间 = 基类对象大小 + 派生类独有的非static数据成员大小(注意复杂结构的字节对齐) + 每个类的虚函数存储空间;
欢迎大家加C/C++ Linux 技术栈开发群:786177639,一起交流学习。
转载:https://blog.csdn.net/Mr_Xuf/article/details/102004925