找回密码
 立即注册
首页 业界区 业界 C语言结构体中的内存对齐

C语言结构体中的内存对齐

翁谌缜 3 天前
C语言结构体内存对齐

在C语言编程中,结构体是一种非常重要的数据类型,它允许我们将不同类型的数据组合在一起。然而,当涉及到结构体在内存中的存储时,有一个关键的概念——内存对齐,这往往容易被忽视,但却对程序的性能和内存使用有着重要影响。
一、结构体大小计算的“理论”与“实际”差异

首先,我们可能会想当然地认为,结构体的大小就是其所有成员大小的简单相加。比如,有这样一个结构体:
  1. struct studentinfo
  2. {
  3.     char name[128];
  4.     int *p;
  5.     short b;
  6.     int c;
  7.     unsigned int age;
  8.     char sex[20];
  9. };
复制代码
理论上计算各成员大小之和:128 + 8 + 2 + 4 + 4 + 20 = 166字节(在64位系统中,指针int *p占8字节)。但实际通过sizeof运算符计算时,在64位系统下得到的结果是168字节,和理论值存在偏差。这是为什么呢?
二、内存对齐的原因

这就涉及到内存对齐了。计算机为了提高CPU的寻址效率,在存储数据时会进行内存对齐。一般来说,嵌入式系统多采用32位系统,CPU的地址总线是32位,为了提升CPU的工作效率,寻址通常以4字节为单位。当数据宽度不足4字节时,系统会默认提供4字节内存方便CPU寻址,这种方式就是字节对齐。
我们可以通过示意图来理解:
1.png


  • 地址未对齐的情形:CPU读取数据时可能需要多次读取,比如要读取一段数据,可能需要读取3次才能获取完整数据。
  • 地址已对齐的情形:CPU可以更少次数地读取到完整数据,比如2次就可以,大大提高了效率。
所以,计算结构体大小时考虑内存对齐是典型的“以空间换时间”的案例,用少量的内存空间浪费换取CPU寻址效率的提升。
三、结构体大小计算示例

来看一个具体的题目:
  1. //假设是32bit系统
  2. #include <stdio.h>
  3. struct A{
  4.     int i;
  5.     char j;
  6.     char * ptr;
  7.     long Array[100];
  8.     char b[2];
  9.     char * c;
  10. };
  11. int main()
  12. {
  13.     printf("%d\n", sizeof(struct A));
  14.     return 0;
  15. }
复制代码
在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等,用于进行字节对齐以及取消字节对齐。
2.png

例如:
  1. #pragma pack(1)  // 取消字节对齐 64bit系统下
  2. struct studentinfo
  3. {
  4.     char name[10];
  5.     int *p;
  6.     short b;
  7.     char c;
  8.     int a;
  9.     unsigned int age;
  10. };
  11. #pragma pack()  // 恢复字节对齐
复制代码
在取消字节对齐后,计算结构体大小就是各成员大小的简单相加,比如上述结构体计算得到的大小是29字节,相比有内存对齐时的大小,节省了内存空间,不过这是以牺牲CPU寻址效率为代价的。
五、总结

内存对齐是C语言中一个重要的概念,它平衡了内存空间和CPU寻址效率。在大多数情况下,默认的内存对齐能够很好地提升程序性能。但在内存资源极度紧张的场景下,我们可以通过#pragma pack指令来取消内存对齐,实现“按需分配”内存。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册