结构体的内存对齐

内存对齐规则

结构体的内存对齐,遵循以下三个原则(以下“首地址”指相对于结构体地址的偏移量):

  1. 第一个成员的首地址为0
  2. 每个成员的首地址是自身大小的整数倍
  3. 结构体的总大小,为其成员中所含最大类型的整数倍

下面以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;
}

// 运行结果
// 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

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;
}

// 运行结果
// 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

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;
}

// 运行结果
// 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

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;
}

// 运行结果
// 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

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;
}

// 运行结果
// 0
// -2485896
// 2
// 4

位域

在声明结构体时,可以指定其成员占用存储空间的二进制位数,这样的成员称为位域。

1
2
3
4
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;
}

// 运行结果
// 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个字节

共用体与结构体

共用体是一种成员共享存储空间的结构体类型。
共用体所占的内存是所有成员内存长度的最大值。

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;
}

// 运行结果
// 8
// 4

  • 结构体按规则对齐,故A所占字节为8
  • 公用体因为每个元素都共享一段内存空间,因此B所占字节数是长度最大成员的字节数,占4字节

版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!
个人邮箱: wushiyu0314@163.com