字节对其详解

什么是字节对齐

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>

struct{
int x;
char y;
} s;

int main()
{
printf("%d\n",sizeof(s)); // 输出8
return 0;
}

计算机中内存大小的基本单位是字节(byte),理论上来讲,可以从任意地址访问某种基本数据类型,但是实际上,计算机并非逐字节大小读写内存,而是以2,4,或8的 倍数的字节块来读写内存,如此一来就会对基本数据类型的合法地址作出一些限制,即它的地址必须是2,4或8的倍数。那么就要求各种数据类型按照一定的规则在空间上排列,这就是对齐。

对齐准则

  • 结构体变量的首地址能够被其对齐字节数大小所整除。
  • 结构体每个成员相对结构体首地址的偏移都是成员大小的整数倍,如不满足,对前一个成员填充字节以满足。
  • 结构体的总大小为结构体对齐字节数大小的整数倍,如不满足,最后填充字节以满足。

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。gcc中默认#pragma pack(4),可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变这一系数。

有效对其值:是给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个。有效对齐值也叫对齐单位。

  1. 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
  2. 结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节
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
#include <stdio.h>
struct {
int i;
char c1;
char c2;
} x1;

struct {
char c1;
int i;
char c2;
} x2;

struct {
char c1;
char c2;
int i;
} x3;

int main() {
printf("%d\n", sizeof(x1)); // 输出8
printf("%d\n", sizeof(x2)); // 输出12
printf("%d\n", sizeof(x3)); // 输出8
return 0;
}

linux下默认#pragma pack(4),且结构体中最长的数据类型为4个字节,所以有效对齐单位为4字节。

以x2来分析其内存布局:

首先使用规则1,对成员变量进行对齐:

  1. sizeof(c1) = 1 <= 4(有效对齐位),按照1字节对齐,占用第0单元;
  2. sizeof(i) = 4 <= 4(有效对齐位),相对于结构体首地址的偏移要为4的倍数,占用第4,5,6,7单元;
  3. sizeof(c2) = 1 <= 4(有效对齐位),相对于结构体首地址的偏移要为1的倍数,占用第8单元;

然后使用规则2,对结构体整体进行对齐:

s2中变量i占用内存最大占4字节,而有效对齐单位也为4字节,两者较小值就是4字节。因此整体也是按照4字节对齐。由规则1得到s2占9个字节,此处再按照规则2进行整体的4字节对齐,所以整个结构体占用12个字节。

g

如果前面加上#pragma pack(2),有效对齐值为2字节,此时根据对齐规则,三个结构体的大小应为6,8,6。内存分布图如下:

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
#pragma pack(2)
#include <stdio.h>
struct {
int i;
char c1;
char c2;
} x1;

struct {
char c1;
int i;
char c2;
} x2;

struct {
char c1;
char c2;
int i;
} x3;

int main() {
printf("%d\n", sizeof(x1)); // 输出6
printf("%d\n", sizeof(x2)); // 输出8
printf("%d\n", sizeof(x3)); // 输出6
return 0;
}