结构体对齐内存的规则是什么?如何计算一个结构体占用空间?
内存对齐规则
结构体的内存对齐,遵循以下三个原则(以下“首地址”指相对于结构体地址的偏移量):
- 第一个成员的首地址为0
- 每个成员的首地址是自身大小的整数倍
- 结构体的总大小,为其成员中所含最大类型的整数倍
下面以32位机器为例,通过几个例子进行说明
例1.1
#include<iostream>
using namespace std;
struct TEST{
char a;
char b;
char c;
};
int main(){
TEST test;
cout<<(int)&(test.a) - (int)&test<<endl;
cout<<(int)&(test.b) - (int)&test<<endl;
cout<<(int)&(test.c) - (int)&test<<endl;
cout<<sizeof(TEST)<<endl;
return 0;
}
// 运行结果
// 0
// 1
// 2
// 3
- a为第一个元素,首地址为0(规则1)
- b的首地址可以是1、2、3、4,就近置于1(规则2)
- c的首地址可以是1、2、3、4,但1为b的首地址,故置于2(规则2)
- 因为结构体中,最大成员占1字节,故结构体TEST占3字节(规则3)
例1.2
#include<iostream>
using namespace std;
struct TEST{
char a;
short b;
char c;
};
int main(){
TEST test;
cout<<(int)&(test.a) - (int)&test<<endl;
cout<<(int)&(test.b) - (int)&test<<endl;
cout<<(int)&(test.c) - (int)&test<<endl;
cout<<sizeof(TEST)<<endl;
return 0;
}
// 运行结果
// 0
// 2
// 4
// 6
- a为第一个元素,首地址为0(规则1)
- b占2字节,故首地址可以是2、4、6,就近置于2(规则2)
- c占1字节,故首地址可以是4、5、6,置于4(规则2)
- 因为结构体中,最大成员占2字节,故结构体TEST占6字节(规则3)
例1.3
#include<iostream>
using namespace std;
struct NUM{
char b;
double c;
};
struct TEST{
char a[3];
NUM num;
int *d;
};
int main(){
TEST test;
cout<<(int)&(test.a) - (int)&test<<endl;
cout<<(int)&(test.num.b) - (int)&test<<endl;
cout<<(int)&(test.num.c) - (int)&test<<endl;
cout<<(int)&(test.d) - (int)&test<<endl;
cout<<sizeof(TEST)<<endl;
return 0;
}
// 运行结果
// 0
// 8
// 16
// 24
// 32
- a虽然为一个数组,但是第一个成员,因此首地址为0,其中三个元素分别按char类型填充1字节
- 成员num为一个结构体,他的首元素b类型为char,占1字节,其中最大的成员是double类型,占8个字节。这时,结构体的首元素需要按8字节去对齐,故地址可以是8、16、24,置8
- num.c为double类型,占8字节,按规则起始位置为16
- 成员d为指针,32为机器上,指针占4字节,按规则其实位置之为24
- 最后结构体的大小,需要为最大类型的整数倍,包括所含结构体中的成员,因此TEST按8字节(double)对齐,为32字节
宏申明:#pragma pack
利用宏申明,可以指定结构体的对齐方式:
- 按n字节对齐:#pragma pack(n),n的值可以取 '1','2','4','8',或者'16'
- 当成员所占字节大于所指定的n字节时,则其首地址须为n的倍数
- 当成员所占字节小于所指定的n字节时,则其首地址仍是自身所占字节的整数倍
- 当结构体中最大的元素所占字节数大于n时,则结构体的总长度须为n的倍数
- 当n大于结构体中最大的元素所占字节数时,则指定不生效
- 取消指定:#pragma pack(),则按默认方式对齐
例2.1
#include<iostream>
using namespace std;
#pragma pack(2)
struct TEST{
char a;
int b;
short c;
};
#pragma pack()
struct TEST_1{
char a;
int b;
short c;
};
#pragma pack(16)
struct TEST_2{
char a;
int b;
short c;
};
int main(){
TEST test;
cout<<(int)&(test.a) - (int)&test<<endl;
cout<<(int)&(test.b) - (int)&test<<endl;
cout<<(int)&(test.c) - (int)&test<<endl;
cout<<sizeof(TEST)<<endl;
cout<<"-----------------------"<<endl;
TEST_1 test_1;
cout<<(int)&(test_1.a) - (int)&test_1<<endl;
cout<<(int)&(test_1.b) - (int)&test_1<<endl;
cout<<(int)&(test_1.c) - (int)&test_1<<endl;
cout<<sizeof(TEST_1)<<endl;
cout<<"-----------------------"<<endl;
TEST_2 test_2;
cout<<(int)&(test_2.a) - (int)&test_2<<endl;
cout<<(int)&(test_2.b) - (int)&test_2<<endl;
cout<<(int)&(test_2.c) - (int)&test_2<<endl;
cout<<sizeof(TEST_2)<<endl;
return 0;
}
// 运行结果
// 0
// 2
// 6
// 8
// -----------------------
// 0
// 4
// 8
// 12
// -----------------------
// 0
// 4
// 8
// 12
#pragma pack(2)
即结构体内存按2字节对齐- test.a为首成员,故其地址为0,占1个字节
- test.b为int类型占4字节,大于2字节,故其首地址须为2的倍数,所以它的地址为2,占4个字节
- test.c为short类型占2字节,其首地址须为2的倍数,所以它的地址为6,占2个字节
- 因为TEST结构中最大成员所占的字节数为4,大于2,因此,结构体总长度须为2的倍数,故占6个字节
- 取消指定:#pragma pack(),则按默认方式对齐,因此TEST_1占12(1+3+4+2+2)字节
#pragma pack(16)
指定结构体TEST_2按16字节对齐,但其中最大成员所占字节仅为4,故不生效
C++11关键字:alignas、alignof
c++11以后引入两个关键字 alignas与 alignof。其中,alignof可以计算出类型的对齐方式,alignas可以指定结构体的对齐方式。
- alignas(n)关键字调整的是整个结构体所占内存的对齐方式,即结构体所占内存的大小必须是n的倍数
- 最小是8字节对齐,可以是16,32,64,128
- 当n小于结构体中最大成员所占字节数时,指定不生效
例3.1
#include<iostream>
using namespace std;
struct TEST{
char a;
int b;
short c;
};
struct alignas(8) TEST_1{
char a;
int b;
short c;
};
struct alignas(2) TEST_2{
char a;
int b;
short c;
};
int main(){
TEST test;
cout<<"alignof(test):"<<alignof(test)<<endl;
cout<<(int)&(test.a) - (int)&test<<endl;
cout<<(int)&(test.b) - (int)&test<<endl;
cout<<(int)&(test.c) - (int)&test<<endl;
cout<<sizeof(TEST)<<endl;
cout<<"-----------------------"<<endl;
TEST_1 test_1;
cout<<"alignof(test_1):"<<alignof(test_1)<<endl;
cout<<(int)&(test_1.a) - (int)&test_1<<endl;
cout<<(int)&(test_1.b) - (int)&test_1<<endl;
cout<<(int)&(test_1.c) - (int)&test_1<<endl;
cout<<sizeof(TEST_1)<<endl;
cout<<"-----------------------"<<endl;
TEST_2 test_2;
cout<<"alignof(test_2):"<<alignof(test_2)<<endl;
cout<<(int)&(test_2.a) - (int)&test_2<<endl;
cout<<(int)&(test_2.b) - (int)&test_2<<endl;
cout<<(int)&(test_2.c) - (int)&test_2<<endl;
cout<<sizeof(TEST_2)<<endl;
return 0;
}
// 运行结果
alignof(test):4
0
4
8
12
-----------------------
alignof(test_1):8
0
4
8
16
-----------------------
alignof(test_2):4
0
4
8
12
- 以上三个结构体内部成员的首地址都按对齐规则排列着
- 结构体TEST没有指定对齐方式,因此结构体总长度为4的倍数
- 结构体TEST_1指定对齐方式为8,大于最大成员所占字节数(int,4字节),故结构体总长度应对齐成8的倍数,为16
- 结构体TEST_2指定对齐方式为2,小于最大成员所占字节数(int,4字节),故结构体总长度仍应为4的倍数,为12,alignof检查对齐方式也为4字节对齐
有static修饰成员的结构体
- 被static修饰的结构体成员称为静态成员。
- 静态成员是多个结构体对象共有的成员
- 静态成员存储在全局(静态)存储区,为多个结构体所共有,不占用结构体的大小
例4.1
#include<iostream>
using namespace std;
struct TEST{
char a;
static int b;
short c;
};
int TEST::b = 1; // 静态成员初始化
int main(){
TEST test;
cout<<(int)&(test.a) - (int)&test<<endl;
cout<<(int)&(test.b) - (int)&test<<endl;
cout<<(int)&(test.c) - (int)&test<<endl;
cout<<sizeof(TEST)<<endl;
return 0;
}
// 运行结果
// 0
// -2485896
// 2
// 4
位域
在声明结构体时,可以指定其成员占用存储空间的二进制位数,这样的成员称为位域。
struct TEST{
int a:3
int b:4
};
TSET中定义两个位域成员a、b,他们分别占int类型8位中的3位和4位,因此结构体只需要申请一个int类型的存储空间即可容纳
- 本质上,位域是按其类型所对应的存储单位存放的,即将位域存放在一个单元里
- 若位数不够时,则再分配一个单元
- 位域的长度不能超过其类型所占的长度,且必须存储在听一个存储单元里
- 如果剩余的内存单元不能容下下一个位域则空闲不用
例5.1
#include<iostream>
using namespace std;
struct TEST{
int a;
char b:2;
char c:3;
char d;
char e:6;
char f:7;
int g;
};
int main(){
TEST test;
cout<<(int)&(test.a) - (int)&test<<endl;
cout<<(int)&(test.d) - (int)&test<<endl;
cout<<(int)&(test.g) - (int)&test<<endl;
cout<<sizeof(TEST)<<endl;
return 0;
}
// 运行结果
// 0
// 5
// 8
// 12
- 结构体TEST的首元素的地址为0,占4个字节
- 第2、3个成员为char类型的位域,分别占2、3位,因此分配了1个char类型的存储空间,有3位为空闲位
- 第4个成员为char类型,地址为5,占一个字节
- 第5、6个成员为char类型的位域,分别占6、7位,需要分配2个char类型的存储空间,第一个成员占6位,空2位;第二个成员占7位,空1位
- 第7个成员位int类型,地址为8,占4个字节
- 结构体中,最大成员占4个字节,因此结构体的总长度应为4的倍数,所以占12个字节
共用体与结构体
共用体是一种成员共享存储空间的结构体类型。
共用体所占的内存是所有成员内存长度的最大值。
#include<iostream>
using namespace std;
struct A{
int m;
char a,b;
short n;
};
union B{
int m;
char a,b;
short n;
};
int main(){
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
return 0;
}
// 运行结果
// 8
// 4
- 结构体按规则对齐,故A所占字节为8
- 公用体因为每个元素都共享一段内存空间,因此B所占字节数是长度最大成员的字节数,占4字节