结构体内存对齐
结构体内存对齐用于计算结构体的大小。
(1)对齐规则
1))结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。
2))其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数==编译器默认的对齐数与该成员变量类型大小的较小值。
vs编译器对齐数默认值为8.
linux中gcc没有默认对齐数,对齐数就是成员变量自身的大小。
3))结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。
4))如果结构体嵌套了结构体,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
例如://vs2022环境中
struct s2
{
char c1;
char c2;
int i;
};
0,1,2,3...分别为地址编号,同时也可以认为是偏移量。
分析;c1是结构体的第一个成员,则对齐到和结构体变量起始位置偏移量为0的地址处。而char c2;c2占一个字节,vs2022默认对齐数为8,1和8最小值为1,所以c2对齐到1的整数倍的地址处,即对齐到地址为1的地址处。int i ;i占4个字节,vs2022默认对齐数为8,4和8的最小值为4,所以i对齐到4的整数倍的地址处,即对齐到地址为4的地址处,则由此可得出struct s2的最小大小是8,而结构体的总大小是最大对齐数的整数倍,对于struct s2而言,最大对齐数为4,8是4的整数倍,所以struct s2的总大小是8.
(2)为什么存在内存对齐?
1))平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2))性能原因
数据结构(尤其是栈)应该尽可能地在自然界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则我们可能需要指向两次内存访问,因为对象可能被分放在两个8字节的内存块中。
总的来说:结构体的内存对齐是拿空间来换取时间的做法。
例如:
struct s
{
char c;int i ;
};
未对齐的情况:
对齐的情况:
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:让占用空间小的成员尽量集中在一起。
struct s1
{
char ch1;
char c2;
int i;};
struct s2
{
char ch1;
int i;
char ch2;
};
s1和s2中的成员是一模一样,但s1和s2所占空间的大小有一些区别。
(3)修改默认对齐数
#pragma这个预处理指令,可以改变编译器的默认对齐数。
例如:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#pragma pack(1)//设置默认对齐数为1;
struct s
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对齐数,还原为默认,对齐数默认为8
结构体在对齐方式上不合适的时候,我们可以自己更爱默认对齐数。一般设置默认对齐数的时候,通常设置为2^n(2的n次方)(n>=0)
注意:Linux系统下不需要修改默认对齐数,因为Linux系统下没有默认对齐数。