C语言结构体内存对齐
在C语言编程中,结构体是一种非常重要的数据类型,它允许我们将不同类型的数据组合在一起。然而,当涉及到结构体在内存中的存储时,有一个关键的概念——内存对齐,这往往容易被忽视,但却对程序的性能和内存使用有着重要影响。
一、结构体大小计算的“理论”与“实际”差异
首先,我们可能会想当然地认为,结构体的大小就是其所有成员大小的简单相加。比如,有这样一个结构体:
struct studentinfo
{char name[128];int *p;short b;int c;unsigned int age;char sex[20];
};
理论上计算各成员大小之和:128 + 8 + 2 + 4 + 4 + 20 = 166
字节(在64位系统中,指针int *p
占8字节)。但实际通过sizeof
运算符计算时,在64位系统下得到的结果是168字节,和理论值存在偏差。这是为什么呢?
二、内存对齐的原因
这就涉及到内存对齐了。计算机为了提高CPU的寻址效率,在存储数据时会进行内存对齐。一般来说,嵌入式系统多采用32位系统,CPU的地址总线是32位,为了提升CPU的工作效率,寻址通常以4字节为单位。当数据宽度不足4字节时,系统会默认提供4字节内存方便CPU寻址,这种方式就是字节对齐。
我们可以通过示意图来理解:
- 地址未对齐的情形:CPU读取数据时可能需要多次读取,比如要读取一段数据,可能需要读取3次才能获取完整数据。
- 地址已对齐的情形:CPU可以更少次数地读取到完整数据,比如2次就可以,大大提高了效率。
所以,计算结构体大小时考虑内存对齐是典型的“以空间换时间”的案例,用少量的内存空间浪费换取CPU寻址效率的提升。
三、结构体大小计算示例
来看一个具体的题目:
//假设是32bit系统
#include <stdio.h>
struct A{int i;char j;char * ptr;long Array[100];char b[2];char * c;
};
int main()
{printf("%d\n", sizeof(struct A));return 0;
}
在32位系统中,各成员的对齐数(自身大小)如下:
int i
:4字节(对齐数4)char j
:1字节(对齐数1)char *ptr
:4字节(指针在32位系统中占4字节,对齐数4)long Array[100]
:每个long
占4字节(对齐数4),数组整体占4×100=400字节char b[2]
:2字节(对齐数1,数组整体占2字节)char *c
:4字节(对齐数4)
分步计算过程:
-
int i
:
从地址0开始存储,占用4字节(地址0~3)。 -
char j
:
对齐数为1,可紧跟在i
之后,从地址4开始,占用1字节(地址4)。 -
char *ptr
:
对齐数为4,需从4的整数倍地址开始。当前已用地址到4,下一个4的整数倍地址是8,因此从地址8开始存储,占用4字节(地址8~11)。
注意:地址5~7为填充空间(3字节,因j
只占1字节,需补齐到4的整数倍才能存放ptr
)。 -
long Array[100]
:
对齐数为4,当前已用地址到11,下一个4的整数倍地址是12,从地址12开始存储,占用400字节(地址12~411)。 -
char b[2]
:
对齐数为1,紧跟Array
之后,从地址412开始,占用2字节(地址412~413)。 -
char *c
:
对齐数为4,需从4的整数倍地址开始。当前已用地址到413,下一个4的整数倍地址是416,从地址416开始存储,占用4字节(地址416~419)。
注意:地址414~415为填充空间(2字节)。
总大小计算:
所有成员存储结束后,最后一个成员c
占用到地址419,此时已用空间为420字节(0~419共420字节)。
由于结构体最大对齐数为4(所有成员的对齐数均不超过4),420是4的整数倍(420÷4=105),满足整体对齐要求。
因此,结构体struct A
的大小为420字节。
四、按需分配内存:取消内存对齐
在某些嵌入式产品中,内存大小极其有限,我们希望内核在分配内存单元时采用“按需分配”的原则,也就是取消内存对齐。这时候可以使用C语言标准的预处理指令#pragma pack(n)
,其中n
的值可以是1、2、4、8等,用于进行字节对齐以及取消字节对齐。
例如:
#pragma pack(1) // 取消字节对齐
struct studentinfo
{char name[10];int *p;short b;char c;int a;unsigned int age;
};
#pragma pack() // 恢复字节对齐
在取消字节对齐后,计算结构体大小就是各成员大小的简单相加,比如上述结构体计算得到的大小是29字节,相比有内存对齐时的大小,节省了内存空间,不过这是以牺牲CPU寻址效率为代价的。
五、总结
内存对齐是C语言中一个重要的概念,它平衡了内存空间和CPU寻址效率。在大多数情况下,默认的内存对齐能够很好地提升程序性能。但在内存资源极度紧张的场景下,我们可以通过#pragma pack
指令来取消内存对齐,实现“按需分配”内存。