内存对齐是计算机系统为了提高寻址效率,在存放基本类型数据时,对其位置做了一定的限制。通常要求这些数据的首地址为某个数的整数倍(通常为4或者8),这就是所谓的内存对齐,这里所说的某个数被称为对齐数。
看一些内存对齐的例子 :
struct A
{
char b;
int a;
char c;
};
int main()
{
printf("%d\n", sizeof(struct A));
system("pause");
return 0;
}
也许你可能认为输出为6,但结果是12.
struct A
{
int a;
char b;
char c;
};
int main()
{
printf("%d\n", sizeof(struct A));
system("pause");
return 0;
}
这次呢,结果是8.
这里面就蕴含着内存对齐的缘故了!
在平时学习中,总觉得指针只要合法,可以指向任何一块内存单元,然后执行相关操作。事实上不是所有的机器都能访问任意地址上的数据的。大多数机器只能访问特定地址处的数据。
那机器为什么要采取这种方式存储呢?不妨这样思考一下。如果b紧挨着a存放,确实节省了不少空间,但是在读取的时候呢?读取a倒不影响,从0起访问一个字节就行了,但访问b的时候就比较麻烦,首先也必须从0 开始,先读取4个内存单元并把第一个内存单元中的a丢掉,然后再读取第五个内存单元,并把这两块内存拼接在一起。这才完整的取出了b。
但如果按照上图所示存储,在存储a之后直接跳过三个字节,然后再存储b这样在读取的时候一次性就可以将b取出来。大大节省了时间。
总结:内存对齐是一种以空间换取时间的做法!
1.第一个结构体成员不用考虑内存对齐。直接存放在与结构体地址偏移量为0的地方。
2.其它成员变量的起始偏移量要偏移到对齐数的最小整数倍处。
对齐数:该成员所占字节的大小 与 编译器默认的对齐数中的较小值。vs中默认值为8.
3.结构体的对齐数是最大对齐数成员的对齐数。
4.结构体的总大小必须是内部最大成员对齐数的最小整数倍。
再来看上面的两道题
struct A
{
char b;
int a;
char c;
};
int main()
{
printf("%d\n", sizeof(struct A));
system("pause");
return 0;
}
首先b不用对齐直接存放a占一个字节,此时的偏移量为1,a的对齐数为4,1不是4的整数倍 ,因此越过三个字节再来存放b,c的对齐数为1,直接存放,此时总共占了9个字节。但还没完,整个结构体的大小必须是内部成员最大对齐数(即b的对齐数4)的最小整数倍,因此整个结构体的大小是12.
struct A
{
int a;
char b;
char c;
};
int main()
{
printf("%d\n", sizeof(struct A));
system("pause");
return 0;
}
首先a不用对齐直接存放,占4个字节,b的对齐数为1, 4是1的整数倍,不用越过 直接存放,此时5个字节,c的对齐数是1, 5是1的整数倍不用越过直接存放,此时占6个字节,结构体的大小必须是内部成员最大对齐数的整数倍因此取8.
接下来我们算一个嵌套结构体的大小。
struct A
{
double a;
int b;
char c;
};
struct B
{
char d;
struct A e;
int f;
};
int main()
{
printf("%d", sizeof(struct B));
system("pause");
return 0;
}
让我们分析一下,对于结构体A,先存放a占8个字节,不用对齐直接存放,此时的偏移量为8,接着存b,b的对齐数为4,刚才的偏移量为8,8是4的整数倍因此直接存放a占4个字节,此时总共12个字节且偏移量为12 ,再存放c,c的偏移量为1,12是1的整数倍直接存放,此时总共占13个字节,结构体的大小必须是内部成员最大偏移量的最小整数倍,因此结构体的大小为16。
然后看结构体B,先存放d,d不用对齐直接存放,占一个字节,此时的偏移量为1,接着存结构体变量e,结构体的对齐数为内部成员的最大对齐数,因此e的对齐数为8,1不是8的整数倍 ,因此跳过7个字节存放e,e的大小为16个字节,此时总的字节数为24,偏移量也是24,再存放f,f的对齐数为4,24是4的整数倍,因此不用对齐直接存放,此时总的字节数为28,又因为结构体大小必须是内部成员最大对齐数最小的整数倍,因此结构体的大小为32.
结构也确实是32.
那么数组也是聚合类型,为什么数组不用考虑内存对齐呢?
这是因为数组的所有成员类型都是一样的,第一个元素不用对齐直接存放。因为元素大小一样故偏移量必然和后面元素的对齐数相等。因此数组是不用考虑内存对齐的。
联合体又称共用体指的是所有元素共用同一快内存空间。这块空间必须足以容纳联合体成员中最大的那个成员。
那么联合体需不需要内存对其呢?也许你觉得既然联合体的所有元素都共用通一块内存空间自然是不需要内存对齐的。但如果联合体的成员中有一个是结构体呢?因此联合体也必须内存对齐。联合体的对齐规则只有结构体的对齐规则中的一条。即联合体的总大小也必须是内部成员的最大对齐数的最小整数倍。
union Un
{
char arr[7];
int a;
};
int main()
{
printf("%d\n", sizeof(union Un));
system("pause");
return 0;
}
结果为 8;
首先数组占7个字节 但联合体的大小必须是内部成员的最大对齐数的最小整数倍。因此是8。
union Un
{
char arr[9];
double a;
};
int main()
{
printf("%d\n", sizeof(union Un));
system("pause");
return 0;
}
输出:16
首先大小应该是9,内部成员的最大对齐数为8,因此结果是16.
如果机器的默认对齐数不满足我们的要求,我们可以利用#pragma这个预处理指令进行修改。
具体用法#pragma pack(n) 这里的n只能 取1,2,4,8,16,如果n不是这些数,则为默认值。
#pragma pack()取消设置的对齐数,还原为默认对齐数。
代码说明
#pragma pack(1)
struct A
{
char a;
int b;
char c;
};
int main()
{
printf("%d\n", sizeof(struct A));
system("pause");
return 0;
}
修改默认对齐数为1,输出结果为6.
#pragma pack(1)
#pragma pack()
struct A
{
char a;
int b;
char c;
};
int main()
{
printf("%d\n", sizeof(struct A));
system("pause");
return 0;
}
取消设置对齐数,对齐数恢复为默认对齐数,输出结果为12.