C语言之玩转结构体2——字节对齐

在这里插入图片描述

一、前言

大家都知道,不同的数据类型在内存中占的空间大小是不一样的,如char占1个字节,short占两个字节,int占四个字节等。那如果把这些数据类型放在同一个结构体中,结构体的大小是否就刚好等于这些数据类型的大小之和呢?答案是不一定的,因为系统可能会对结构体存储空间进行优化,以提高访问速度,这其中涉及到的知识就是字节对齐。

二、名词解释

1.1、什么是字节对齐?
在现代计算机中,内存空间都是按照字节(byte)划分的。从理论上讲对任何类型的变量的访问可以从任何地址开始,但实际情况是,访问特定类型的变量的时候经常在待定的内存地址访问,这就需要各种类型的数据按照一定规则在空间上排列,而不是顺序地一个接一个地排放,这种所谓的规则就是字节对齐。
1.2、几个名词解释。
自身对齐值:数据类型本身的对齐值,如char类型是1,short类型是2。

指定对齐值:编译器或程序员指定的对齐值,如c-free默认对齐值是8;32位单片机的对齐值是4;可以通过伪指令#pragma pack(n)来改变这个默认值。

#pragma pack(4)//指定4字节对齐
typedef struct _student3_t
{
  char a;
  double b;
  char c;
}student3_t;
#pragma pack()

有效对齐值:自身对齐值和指定对齐值中较小的那个。如double自身对齐值是8,指定对齐值是4,则它的有效对齐值就是4。

三、字节对齐的规则

字节对齐有两个规则:
1、存放成员的起始地址必须是该成员有效对齐值的整数倍。
2、不但结构体的成员有有效对齐值,结构体本身也有有效对齐值,根据规则1算完成员占用空间后,最后要将其补齐为该结构体有效对齐值的整数倍。结构体的有效对齐值是其最大数据成员的自身对齐值。

下面我们来通过一个例子,看下编译器是如何进行字节对齐的。

四、实战练习

我这里以C-free编译器(指定对齐值是8),列举个例子:

//demo1
typedef struct _student_t
{
  int a;   //0x0000-0x0004
  char b;  //0x0004-0x0005
  short c; //0x0006-0x0007
  double d; //0x0008-0x0016
  char e;   //0x0016-0x0017
}student_t;
int main(void)
{
  printf("%d\r\n",sizeof(student_t));
  return 0;  
}

得到运行结果是24,那这个24是怎么来的呢?怎么不是4+1+2+8+1 =16?
下面我们来分析:
我们假定该结构体的首地址为0x0000

a成员的自身对齐值是4,根据指定对齐值是8,算出它的有效对齐值是4(4和8中较小的那个),它的起始地址0x0000是4的整数倍,满足规则1,故它占有4个字节。此时地址用了0x0000-0x0004.

b成员的自身对齐值是1,指定对齐值是8,有效对齐值是1,它的起始地址0x0004是1的整数倍,故它占有1个字节,地址用了0x0004-0x0005.

c成员的自身对齐值是2,指定对齐值是8,有效对齐值是2,它的起始地址是0x0005,不是2的整数倍,因此需要在b成员后填充一个字节,让c成员的起始地址为0x0006,故它占有2个字节,地址用了0x0006-0x0007

以此类推:
d成员的自身对齐值是8,指定对齐值是8,有效对齐值是8,起始地址是0x0008-0x0010

e成员的自身对齐值是1,指定对齐值是8,有效对齐值是1,起始地址是0x0011-0x0012

结构体A的有效对齐值是其最大数据成员的自身对齐值,也就是d成员的8,根据规则2,算下0x0012 = 18不是8的倍数,需要在末尾补齐,也就是补到24个字节即可。

故最后算的该结构体的长度为24.

结构体的成员位置可能会影响到结构体大小。

我们如果将上述结构体成员换个顺序:

typedef struct _student_t
{
  char b;   
  char e;  
  short c;  
  int a;   
  double d; 
}student_t;
int main(void)
{
  printf("%d\r\n",sizeof(student_t));
  return 0;  
}

b成员的自身对齐值是1,指定对齐值是8,有效对齐值是1,起始地址是0x0000-0x0001.

e成员的自身对齐值是1,指定对齐值是8,有效对齐值是1,起始地址是0x0001-0x0002.

c成员的自身对齐值是2,指定对齐值是8,有效对齐值是2,起始地址是0x0002-0x0004.

a成员的自身对齐值是4,指定对齐值是8,有效对齐值是4,起始地址是0x0004-0x0008.

d成员的自身对齐值是8,指定对齐值是8,有效对齐值是8,起始地址是0x0008-0x00010.

按照上述同样的规则,算得结构体长度为16.

我们发现这次编译器没有为结构体填充任何空间,在定义结构体时,我们要尽量达到这种效果。

我们再来看一个内嵌数组的例子

typedef struct _student_t
{
  char a;  //0 - 1 
  char b[8]; //1 - 9
  int c;   //12-16
}student_t;
int main(void)
{
  printf("%d\r\n",sizeof(student_t));
  return 0;
}

运行结果是16
在这里插入图片描述
这里其实b数组,我们可以看成是8个char类型的变量,然后往下面继续算即可。

我们再来看一个内嵌结构体的例子

typedef struct _student1_t
{
  int a;
  short b;
  float d;
}student1_t;

typedef struct _student_t
{
  char a;  
  student1_t student;
  short d;
}student_t;

int main(void)
{
  printf("%d\r\n",sizeof(student_t));
  return 0;
}

算得结构体的大小为20
在这里插入图片描述
这里计算的时候,同样的我们可以把这个内嵌的结构体成员直接搬过来进行计算即可,它大小等同于下面:

typedef struct _student_t
{
  char a;  
  int c;
  short b;
  float e;
  short d;
}student_t;

int main(void)
{
  printf("%d\r\n",sizeof(student_t));
  return 0;
}

最后,我们加上伪指令,改变上述结构体的指定对齐值为2,看看效果:

#pragma pack(2)
typedef struct _student_t
{
  char a;  //0 - 1 
  int c;
  short b;
  float e;
  short d;
}student_t;
#pragma pack()
int main(void)
{
  printf("%d\r\n",sizeof(student_t));
  return 0;
}

运行得到结构体大小是14
在这里插入图片描述
我们看到,结构体的大小由20变成了14,改变指定对齐值影响到了该结构体的大小。

原因就是成员的有效对齐值因为指定对齐值变小而变小了。

根据规则1:存放成员的起始地址必须是该成员有效对齐值的整数倍,这样我们就可以用较少空间来存放该结构体。

五、结语

如您在使用过程中有任何问题,请加QQ群进一步交流。

QQ交流群:906015840 (备注:物联网项目交流)

静晨出品:静之所想,晨之所计
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值