c语言内存对齐

结构体对齐原因

结构体对齐原因有很大部分是因为计算机扫描的内存单元个数,也就是数据总线的大小。
内存对齐的问题主要存在于理解struct等复合结构在内存中的存储结构。
在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,但不一定是相邻存储,第一个成员的地址和整个结构的地址相同。

对齐的原则

原则1:数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员obj存储的起始位置要从该成员obj大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
原则2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)
原则3:收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。如果是结构体B包含了结构体A对象a,判断最大成员时并不是a,而是a结构体的最大成员。
(补:上述取最大成员的大小后,实际上应该取[#pragma pack指定的数值]与[最大成员的数值]比较小的那个为准)

这三个原则具体怎样理解呢?我们看下面几个例子,通过实例来加深理解。
例1:

1
2
3
4
5
6
7
8
9
struct{
short a1;
short a2;
short a3;
}A;
struct{
long a1;
short a2;
}B;

sizeof(A) = 6; 这个很好理解,三个short都为2。
sizeof(B) = 8; 这个比是不是比预想的大2个字节?long为4,short为2,整个为8,因为原则3。

例2:

1
2
3
4
5
6
7
8
9
10
struct A{
int a;
char b;
short c;
};
struct B{
char b;
int a;
short c;
};

sizeof(A) = 8; int为4,char为1,short为2,这里用到了原则1和原则3。
sizeof(B) = 12; 是否超出预想范围?char为1,int为4,short为2,怎么会是12?还是原则1和原则3。

深究一下,为什么是这样,我们可以看看内存里的布局情况。

1
2
3
4
5
             a         b        c
A的内存布局:1111, 1*, 11

b a c
B的内存布局:1***, 1111, 11**

其中星号*表示填充的字节。
A中,b后面为何要补充一个字节?因为c为short,其起始位置要为2的倍数,就是原则1。c的后面没有补充,因为b和c正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。
B中,b是char为1,b后面补充了3个字节,因为a是int为4,根据原则1,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数,c后面不补充,整个B的空间为10,不符,所以要补充2个字节。

再看两个结构中含有结构成员的例子:
例3:

1
2
3
4
5
6
7
8
9
10
11
12
struct A{
int a;
double b;
float c;
};
struct B{
char e[2];
int f;
double g;
short h;
struct A i;
};

sizeof(A) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24。
sizeof(B) = 48; 看看B的内存布局。

1
2
             e    f     g        h        i
B的内存布局:11**,1111, 11111111,11******,1111****, 11111111, 1111****

例4:

1
2
3
4
5
6
7
8
9
10
struct A{
int m1;
int *m2;
}a;
struct B{
int m1;
struct A m2;
}b;
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(b));

输出16 24。原则2,A的存储位置将由8开始,所以结构体B的成员b.m2.m1并不会与b.m1同存储于前八个字节中;原则3,结构体的总大小并不是取决于结构体A的大小,而是A结构体的最大成员,所以总大小是24而不是32。

为什么要设计内存对齐

考虑一个问题,为什么要设计内存对齐的处理方式呢?
引入内存对齐的原因一方面在于硬件取指的方便,例如在32位总线系统上,如果一个int变量(4字节)放在一个4的倍数开始的内存地址中,则CPU可以一次将其数值读出,否则的话就要分两次才能读出。另一个重要的原因在于移植性的要求,也就是说不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。引入内存对齐的目的主要是为了可移植性以及最大限度提升硬件性能。详细可参看如下链接http://www.doc88.com/p-032414262099.html

设计结构体习惯

最后顺便提一点,在设计结构体的时候,一般会遵照一个习惯,就是把占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对节约一些对齐空间。

另外,测试得知,假如结构体如下:

1
2
3
4
5
6
struct execcmd{
int type;
char *argv[10];
};
struct execcmd *cmd;
printf("%d %d\n",sizeof(cmd->argv),sizeof(*cmd));

将输出80,88,指针(占8个字节)数组argv开始位置并不是以整个数组为准,而是以数组元素为准。
内容引用于:c中的内存对齐

关于union大小的计算

union联合体是可以(在不同时刻)保存不同类型和长度的对象的变量,通过单块的存储区存放不同类型的数据,各个成员分享共同的存储空间,联合的长度由两点决定:(1)要满足大于等于最大的成员长度;(2)且是满足(1)条件下最大的成员类型(union的对齐大小)的最小倍数。
存放变量时将会从内存中的同一位置开始,后面的变量将会覆盖以前的变量,但是如果占用的内存小于前者,那么以前未被覆盖的内存存储对象将会保持不变。

显示 Gitment 评论