内存对齐的原因
1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
对齐规则
1、对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类型的整数倍
2、结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍
3、如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型
考虑下面一个实例:
对于下面这个复杂的例子
为了分析这个例子,先来看一下union的几个例子
可以看到u1中最大的成员是double型所以占用8字节。但是u2和u3为什么分别是16和13呢?u2中int类型为最大,使u2的对齐方式变为4,也就是说,u2的大小必须在4的对界上,所以占用的空间变成了16(最接近13的对界)。而u3中都是char,所以sizeof(u3)为13。
再来看union嵌套在struct中的例子
结构体N中的联合UN中double类型最大,因此buffer需要与8补齐,补齐后也为16;同时由于union类型比较特殊,计算union成员的偏移量时,需要根据union内部最大成员类型来进行缓冲补齐,所以为了保证偏移量为union最大成员double类型的整数倍,需要在num(short类型)后面填充6个字节。此时加上前面num 2字节为24字节。
最后再来看一下那个复杂的例子
sizeof(Q)=40
在结构体P中,联合体UB中buff为了与num对齐,补3后为16;此时a的偏移为20,符合规则1;d的偏移量为24,符合规则1,故P的字节数为32。
在结构体Q中,联合体UB中buff为了与num对齐,补3后为16;此时d的偏移为20,不符合规则1,因此buff再补4,为此时d的偏移为24,符合规则1;a的偏移为32,符合规则1;此时结构体Q的字节数为36,不符合规则2,因此对齐后为40字节。
柔性数组
C99中定义了柔性数组机制,因此对于一个结构体,如果最后一个成员是数组的话,结构体大小与该成员是否是柔性数组有密切关系。
而下面的结构体ab的sizeof(ab) = 12,因为最后一个数组成员是普通数组,适用于上述补齐规则。
1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
对齐规则
1、对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类型的整数倍
2、结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍
3、如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型
考虑下面一个实例:
struct A{
char a;
int b;
short c;
};
struct B{
short c;
char a;
int b;
};
sizeof(A)=12
sizeof(B)=8
对于结构体A来说,其成员a的偏移量为0,字节数为1;b的偏移量为1,字节数为4;根据规则1,b的当前偏移量不是不是其成员类型的整数倍,故在a后补3字节,此时b的偏移量为4,符合要求。由于a补了3字节,成员c当前偏移量为8,其字节数为2,符合要求,因此A为10字节,再根据规则2,结构体占用内存大小是结构体内最大数据成员的最小整数倍,最大数据成员为4字节,故结构体A大小为12字节。对于下面这个复杂的例子
struct C
{
int num;
union UB
{
char buff[13];
int num;
}un;
double d;
int a;
}buf;
sizeof(buf)=40
为了分析这个例子,先来看一下union的几个例子
union u1
{
double a;
int b;
};
union u2
{
char a[13];
int b;
};
union u3
{
char a[13];
char b;
};
cout<<sizeof(u1)<<endl; //8
cout<<sizeof(u2)<<endl; //16
cout<<sizeof(u3)<<endl; //13
union联合体其大小取决于所有成员中,占用空间最大的一个成员的大小。可以看到u1中最大的成员是double型所以占用8字节。但是u2和u3为什么分别是16和13呢?u2中int类型为最大,使u2的对齐方式变为4,也就是说,u2的大小必须在4的对界上,所以占用的空间变成了16(最接近13的对界)。而u3中都是char,所以sizeof(u3)为13。
再来看union嵌套在struct中的例子
struct M
{
short num;
union UM
{
char buffer[13];
int num;
}ubuf;
}m;
struct N
{
short num;
union UN
{
char buffer[13];
double num;
}ubuf;
}n;
sizeof(m)=20
sizeof(n)=24
从上面的例子可以知道结构体M中的联合UM中的int类型最大,因此buffer需要与4补齐,补齐后为16;同时由于union类型比较特殊,计算union成员的偏移量时,需要根据union内部最大成员类型来进行缓冲补齐,所以为了保证偏移量为union最大成员int类型的整数倍,需要在num(short类型)后面填充2个字节。此时加上前面num 2字节为20字节。结构体N中的联合UN中double类型最大,因此buffer需要与8补齐,补齐后也为16;同时由于union类型比较特殊,计算union成员的偏移量时,需要根据union内部最大成员类型来进行缓冲补齐,所以为了保证偏移量为union最大成员double类型的整数倍,需要在num(short类型)后面填充6个字节。此时加上前面num 2字节为24字节。
最后再来看一下那个复杂的例子
struct P
{
int num;
union UB
{
char buff[13];
int num;
}un;
int a;
double d;
};
struct Q
{
int num;
union UB
{
char buff[13];
int num;
}un;
double d
int a;
};
sizeof(P)=32sizeof(Q)=40
在结构体P中,联合体UB中buff为了与num对齐,补3后为16;此时a的偏移为20,符合规则1;d的偏移量为24,符合规则1,故P的字节数为32。
在结构体Q中,联合体UB中buff为了与num对齐,补3后为16;此时d的偏移为20,不符合规则1,因此buff再补4,为此时d的偏移为24,符合规则1;a的偏移为32,符合规则1;此时结构体Q的字节数为36,不符合规则2,因此对齐后为40字节。
柔性数组
C99中定义了柔性数组机制,因此对于一个结构体,如果最后一个成员是数组的话,结构体大小与该成员是否是柔性数组有密切关系。
struct ab{
struct ab* next;
int val;
char buf[0]; //或char buf[]
};
当结构体定义中,最后一个成员是数组且数组大小为0或没标记时,该成员数组是柔性数组,不计入结构体大小,因此sizeof(ab) = 8而下面的结构体ab的sizeof(ab) = 12,因为最后一个数组成员是普通数组,适用于上述补齐规则。
struct ab{
struct ab* next;
int val;
char buf[1];
};
C++为了兼容C,保留了struct关键字,但是实际上C++中的struct是一个默认访问控制权限为public的class。C++标准规定:一个空类的大小为1个字节,因此在C++中,sizeof(空类或空结构体) = 1,在C语言中,sizeof(空结构体) = 0。