首先来看一下内存对齐的例子
#include<iostream>
struct a1
{
char c1;
char c2;
int n;
double d;
};
struct a2
{
char c1;
int n;
char c2;
double d;
};
int main()
{
std::cout<<sizeof(a1)<<std::endl<<sizeof(a2);
}
输出的结果为16和24,按照常规思想,char占一个字节,int占4个字节,double占8个字节,输出的结果应该都是
1
+
1
+
4
+
8
=
14
1+1+4+8=14
1+1+4+8=14,但是结果却不是这样!!这就是由于编译器让变量内存对齐的原因。
内存对齐
内存对齐”应该是编译器的“管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上。引用自百度百科。
通俗的说就是对于结构体中的变量,编译器会开辟一段连续的存储空间,把变量都放在里面,然后编译器为了某些原因不会照顺序的分配内存给变量。对于第一个例子来说不会 0 ∣ 1 ∣ 2345 ∣ 678910111213 ∣ 0|1|2345|678910111213| 0∣1∣2345∣678910111213∣这样分配内存,编译器会改变变量在内存中的位置。这里补充一个知识,在main前声明的结构不占内存,在main里的结构定义计算机才分配内存。
这样做的原因
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。引用自百度百科。
具体如何解释,我想学完编译原理再来补充。
内存对齐的规则
- 数据成员的对齐
偏移量:比如在结构体a1中,c1的偏移量为0,如果不做内存对齐c2的偏移量为1,n的偏移量为2。
C语言还有一个#pragma pack(n)
括号里为常量的编译预处理指令,如果不设置默认为8。对于每一个变量编译器会把它的偏移量设置为min(n, sizeof(这个变量))的整数倍,当然第一个放在偏移量为0位置。
比如第一个例子
c1放在0,c2放在1,对于n不对齐前,其偏移量为2很明显不是 m i n ( 8 , s i z e o f ( i n t ) ) = 4 min(8,sizeof(int))=4 min(8,sizeof(int))=4的倍数,所以编译其会把n右移到4这个位置, 3 , 4 3,4 3,4这两个位置就空出来了,对于d此时它的偏移量恰好为8是min…的倍数。所以最后总的内存是16。分配情况是 0 ∣ 123 ∣ 4567 ∣ 89101112131415 0|123|4567|89101112131415 0∣123∣4567∣89101112131415。 - 结构本身的对齐
在对齐完数据成员后,结构总的偏移量要为min(n,sizeof(结构中所占最大的变量))的倍数,如果不满足也要对齐。这里更前面差不多,可以自行去证明。
最后这个规则对联合跟类也同样使用。注意类中一般还有成员函数,函数同样也要当作变量对齐,但是类中只有虚函数才在类中占据内存,其他不占。