alignment(字节对齐)

什么是字节对齐?

在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”。 比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除。

为什么要进行字节对齐

字节对齐便于快速访问内存,合理的利用字节对齐可以有效地节省存储空间。
对于32位机器来说,4字节对齐能够使cpu访问速度提高。例如一个long类型的变量,如果跨越了4字节边界存储,那么cpu要读取两次,这样效率就低了(为什么)。另外还需要考虑编译器的类型。在vc中默认是4字节对齐的,GNU gcc 也是默认4字节对齐。

如何进行字节对齐

结构体变量在给成员分配内存的时候,是按照字节对齐的方式存储的。以结构体成员中占内存最多的数据类型所占的字节数为标准,所有的成员在分配内存的时候都要与这个长度对齐

几个小例子,可以看出结构体变量在为成员变量分配内存时如何进行字节对齐。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Obj1 {
// 结构体中最长成员数据类型是int,占4个字节,则以4字节对齐
char a;
int b;
char c;
};

void test1() {
Obj1 data;
cout << "sizeof(int):" << sizeof(int) << endl; // sizeof(int):4

//%p是取地址输出控制符
printf("%p, %p, %p\n", &data.a, &data.b, &data.c); //0x7ffe1e35d28c, 0x7ffe1e35d290, 0x7ffe1e35d294
printf("%d\n", sizeof(data)); //12
}

第一个char类型占1个字节,但是需要以4字节对齐;同理第二个char类型占1个字节,但以4字节对齐。于是Obj1类型对象data占用的内存为12字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Obj2 {
// 结构体中最长成员数据类型是double,占8个字节,则以4字节对齐
char a;
double b;
char c;
};

void test2() {
Obj2 data;
cout << "sizeof(double):" << sizeof(double) << endl; //sizeof(double):8

//%p是取地址输出控制符
printf("%p, %p, %p\n", &data.a, &data.b, &data.c); // 0x7ffe1e35d280, 0x7ffe1e35d288, 0x7ffe1e35d290
printf("%d\n", sizeof(data)); // 24
}

这里与上个例子的不同之处在于以8字节对齐,所以占用的内存为8 * 3 = 24字节。

1
2
3
4
5
6
7
8
9
10
11
12
struct Obj3 {
Obj1 o1; // 12 ==> 16
Obj2 o2; // 24
};

void test3() {
Obj3 data;
printf("%p, %p\n", &data.o1, &data.o2); // 0x7ffe98ca7220, 0x7ffe98ca7230

//%p是取地址输出控制符
printf("%d\n", sizeof(data)); // 40
}

参考文献

[1]结构体变量字节对齐:http://c.biancheng.net/view/243.html
[2]为什么要进行字节对齐:https://blog.csdn.net/21aspnet/article/details/6729724
[3]为什么要进行字节:http://blog.chinaunix.net/uid-24118190-id-75219.html