内存对齐规则
结构体的内存对齐,遵循以下三个原则(以下“首地址”指相对于结构体地址的偏移量):
- 第一个成员的首地址为0
- 每个成员的首地址是自身大小的整数倍
- 结构体的总大小,为其成员中所含最大类型的整数倍
下面以32位机器为例,通过几个例子进行说明
例1.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #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; }
|
- 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #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; }
|
- a为第一个元素,首地址为0(规则1)
- b占2字节,故首地址可以是2、4、6,就近置于2(规则2)
- c占1字节,故首地址可以是4、5、6,置于4(规则2)
- 因为结构体中,最大成员占2字节,故结构体TEST占6字节(规则3)
例1.3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #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; }
|
- 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #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; }
|
- #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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #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; }
|
位域
在声明结构体时,可以指定其成员占用存储空间的二进制位数,这样的成员称为位域。
| struct TEST{ int a:3 int b:4 };
|
TSET中定义两个位域成员a、b,他们分别占int类型8位中的3位和4位,因此结构体只需要申请一个int类型的存储空间即可容纳
- 本质上,位域是按其类型所对应的存储单位存放的,即将位域存放在一个单元里
- 若位数不够时,则再分配一个单元
- 位域的长度不能超过其类型所占的长度,且必须存储在听一个存储单元里
- 如果剩余的内存单元不能容下下一个位域则空闲不用
例5.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #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; }
|
- 结构体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个字节
共用体与结构体
共用体是一种成员共享存储空间的结构体类型。
共用体所占的内存是所有成员内存长度的最大值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #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; }
|
- 结构体按规则对齐,故A所占字节为8
- 公用体因为每个元素都共享一段内存空间,因此B所占字节数是长度最大成员的字节数,占4字节