C语言结构体对齐介绍

嵌入式技术

1362人已加入

描述

大家好,我是嵌入式老林,从事嵌入式软件开发多年,今天分享的内容是C语言结构体对齐介绍,希望能对你有所帮助

摘要:最近有粉丝说在笔试的时候,经常遇到求结构体字节数的问题,做完后不知道自己写对了没。这篇文章就来介绍一下结构体对齐的计算方法。不知道你们笔试的时候有没有遇到这种题目呢?

一、字节对齐的基本概念

1.1 什么是字节对齐

在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”。比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除。

1.2 为什么需要字节对齐

当我们在C语言中定义结构体时,编译器会对结构体的成员进行内存对齐,以提高访问效率和节约内存。如果没有对齐的话,CPU在取数的时候,会花更多的指令周期。

一个32位系统,假设有个整型变量的地址不是自然对齐,比如为0x00000002,则CPU取它的值需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short,然后组合得到所要的数据;如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要访问一次就可以取出数据

1.3 结构体对齐的规则

1,结构体的第一个成员永远放在结构体起始位置偏移为0的地址

2,结构体从第二个成员,总是放在一个对齐数的整数倍数

对齐数 = 编译器默认的对齐数和变量自身大小的较小值

3,结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4,如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

二、结构体对齐的计算

2.1 例子一

先来看一下这个结构体占了几个字节,说明一下,下面的例子都是在32bit系统上运行的

#include < stdio.h >
#include < stddef.h >




int main(void)
{
  typedef struct
  {
    char c1;
    int i;
    char c2;
  }MyStruct;
  MyStruct st;
  printf("%d
", sizeof(MyStruct));
  printf("offset c1:%d, i:%d, c2:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, i), offsetof(MyStruct, c2));
  printf("addr c1:%x, i:%x, c2:%x
", &st.c1, &st.i, &st.c2);




  return 0;
 }

运行结果,这个结构体占12个字节

先来介绍一下offsetof()这个宏,如果想知道结构体的某个成员相对于结构体的首地址的偏移量,可通过这个宏获取,这个宏在头文件stddef.h中。我们看到结构体中的成员c1,i,c2分别相对于结构体的首地址偏移0,4,8

cpu

解释:c1按1字节对齐,但i为int类型,按4字节对齐,所以不能紧跟其后,i的地址要为4的整数倍,所以在c1后空出了3字节开始存放,c2为1字节对齐,紧跟在i后面即可,这样算的话,总字节数为9,但结构体的总大小要为最大对齐数的整数倍,这个结构体的最大对齐数就是4,所以得在c2的后面再补3个字节,所以这个结构体就占用了12字节。

假设下图左边那一列是变量存放的地址,右边是存放的变量

cpu

如果将上面例子的结构体成员换一下位置,结果又是怎样的呢?

#include < stdio.h >
#include < stddef.h >


int main(void)
{
  typedef struct
  {
    char c1;
    char c2;
    int i;
  }MyStruct;
  MyStruct st;
  printf("%d
", sizeof(MyStruct));
  printf("offset c1:%d, c2:%d, i:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, c2), offsetof(MyStruct, i));
  printf("addr c1:%x, c2:%x, i:%x
", &st.c1, &st.c2, &st.i);

  return 0;
 }

运行结果:

cpu

解释:c1和c2分别按1字节对齐,所以c2紧跟c1后面,i按4字节对齐,所以空2个字节,再存放i。那么整个结构体大小为8字节,也满足是最大对齐数的整数倍。

实际上,这两个例子不管是32bit还是64bit的系统,结果都是一样的,因为char类型和int类型在32bit和64bit系统中,占用的空间是一样的。当然了,最好是在使用前先用sizeof测一下每种类型在当前环境中占用的大小。

因此,在实际项目开发中,如果定义的结构体有很多成员,尽可能地把同类型的成员放在一起,这样可以节省一些空间。

cpu

2.2 例子二

#include < stdio.h >
#include < stddef.h >


int main(void)
{
  typedef struct
  {
    char c1;
    short s1;
    char c2;
    int i;
    char c3;
  }MyStruct;
  MyStruct st;
  printf("%d
", sizeof(MyStruct));
  printf("offset c1:%d, s1:%d, c2:%d, i:%d, c3:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, s1), offsetof(MyStruct, c2), offsetof(MyStruct, i), offsetof(MyStruct, c3));
  printf("addr c1:%x, s1:%x, c2:%x, i:%x, c3:%x
", &st.c1, &st.s1, &st.c2, &st.i, &st.c3);

  return 0;
 }

运行结果:

cpu

解释:c1为1字节对齐,s1为2字节对齐,地址要为2的整数倍,所以不能紧跟c1后面,得从2开始,c2是1字节对齐,i为4字节对齐,不能从地址5开始存放,否则CPU访问的时候需要很多次,甚至可能会出错。所以要在c2后空出3字节,i从地址8开始,c3为1字节对齐,紧跟其后即可,累加起来为13个字节,结构体总大小又要为最大对齐数整数倍,所以该结构体大小为16

cpu

那么,将同种类型的成员都放在一起,又占用了多少空间呢?

实际结果:

cpu

2.3 例子三

看一下带结构体嵌套的如何计算:

#include < stdio.h >
#include < stddef.h >


int main(void)
{
  typedef struct
  {
    int j;
    char c;
  }MyS1;

  typedef struct
  {
    char c1;
    MyS1 my_s1;
    short s1;
    int i;  
  }MyStruct;
  MyStruct st;
  printf("%d
", sizeof(MyStruct));
  printf("offset c1:%d, my_s1:%d, s1:%d, i:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, my_s1), offsetof(MyStruct, s1), offsetof(MyStruct, i));
  printf("addr c1:%x,  my_s1:%x, s1:%x, i:%x
", &st.c1, &st.my_s1, &st.s1, &st.i);

  return 0;
 }

解释:看了前面几个例子的分析,相信这个结构体嵌套的大家也会,原理是一样的。c1为1字节对齐,嵌套的结构体my_s1中的 j 为4字节对齐,地址要为4的整数倍,所以c1后要空出3个字节,c为1个字节,紧跟 j 后,s1为2字节,在c后面空出2个字节,i 是4个字节,s1后面再空2个字节保持对齐,这样的话,就是 4+4+2+2+4=16,最大对齐数是4,16也是4的整数倍。因此,这个结构体大小为16字节。有没有很多同学会犯这个错误呢?

不要忽略了嵌套结构体的自身的对齐,嵌套的结构体my_s1的最大对齐数为4,因此嵌套的结构体my_s1的结构体大小要为4的整数倍,所以my_s1的结构体大小为8字节,所以,这个结构体的大小为20字节

cpu

运行结果:

cpu

那么将嵌套结构体中的int类型改成double类型,又是多少呢?

#include < stdio.h >
#include < stddef.h >


int main(void)
{
  typedef struct
  {
    double j;
    char c;
  }MyS1;

  typedef struct
  {
    char c1;
    MyS1 my_s1;
    short s1;
    int i;  
  }MyStruct;
  MyStruct st;
  printf("%d
", sizeof(MyStruct));
  printf("offset c1:%d, my_s1:%d, s1:%d, i:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, my_s1), offsetof(MyStruct, s1), offsetof(MyStruct, i));
  printf("addr c1:%x,  my_s1:%x, s1:%x, i:%x
", &st.c1, &st.my_s1, &st.s1, &st.i);

  return 0;
 }

运行结果:

这个我就不带着大家一步一步算了,自己算一下,看下你学会了没

cpu

2.4 例子四

看一下带联合体嵌套的如何计算

#include < stdio.h >
#include < stddef.h >


int main(void)
{

  typedef struct
  {
    char c1;
    union
    {
      int j;
      char c[8];
    }MyUnion;
    short s1;
    int i;  
  }MyStruct;
  MyStruct st;
  printf("%d
", sizeof(MyStruct));
  printf("offset c1:%d, MyUnion:%d, s1:%d, i:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, MyUnion), offsetof(MyStruct, s1), offsetof(MyStruct, i));
  printf("addr c1:%x,  MyUnion:%x, s1:%x, i:%x
", &st.c1, &st.MyUnion, &st.s1, &st.i);

  return 0;
 }

解释:这里要注意一点就是,联合体的各成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权(对该内存的读写),各变量共用一个内存首地址。

cpu

运行结果:

cpu

个人觉得也不用刻意去记这些规则吧,重在理解,记规则的话,很久没用了估计就忘了。多看看,多笔算算就清楚了。看了前面的分析,其实就两点,第一个就是结构体成员变量存放的地址是该变量类型的整数倍;第二点就是结构体的大小为最大对齐数的整数倍。

三、修改对齐方式

3.1 使用伪指令#pragma pack (n)

如果你不想使用编译器的默认对齐方式,可通过以下方式修改结构体的字节对齐方式

在定义的结构体前后加上这两条指令,n表示你想让这个结构体按照几字节对齐。

· 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。

· 使用伪指令#pragma pack (),取消自定义字节对齐方式。

#include < stdio.h >
#include < stddef.h >


int main(void)
{
  #pragma pack (1)
  typedef struct
  {
    char c1;
    short s1;
    int i;  
  }MyStruct;
  #pragma pack ()
  MyStruct st;
  printf("%d
", sizeof(MyStruct));
  printf("offset c1:%d, s1:%d, i:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, s1), offsetof(MyStruct, i));
  printf("addr c1:%x, s1:%x, i:%x
", &st.c1, &st.s1, &st.i);

  return 0;
 }

运行结果:

因为已经设定了按1字节对齐,所以就是,c1为1字节,s1为2字节,i 为2字节,加起来就是 7 字节。

cpu

改成按2字节对齐又是多少呢?

实际就是c1后面再空了一个字节

cpu

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分