C语言中关于sizeof(struct)和sizeof(union)

3,691 阅读5分钟

前言

一般32位机器上各个数据类型所占的存储空间如下:

  • char:8位(1字节)
  • short:16位(2字节)
  • int:32位(4字节)
  • long:32位(4字节)
  • float:32位(4字节)
  • double:64位(8字节)


一、先来看struct结构体

请牢记以下3条原则(在没有#pragma pack宏的情况下):

  • 数据成员对齐原则:结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,之后的每个数据成员存储起始位置要从该成员大小的整数倍开始(前面的若不是当前成员的整数倍,则补齐)(比如int在32位机器上为4字节,所以要从4的整数倍地址开始存储)
  • 结构体作为成员:如果一个结构体里同时包含结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储(如struct a里有struct b,b里有char,int,double等元素,那么b应该从8(即double类型的大小)的整数倍开始存储)
  • 结构体的总大小:即sizeof的结果。在按之前的对其原则计算出来的大小的基础上,必须还得是其内部最大成员的整数倍(该最大成员包括结构体里面的结构体的最大成员),不足的要补齐(如struct里最大为double,现在计算得到的已经是11,则总大小为16)


具体例子:

typedef struct bb
{
        //[0]....[3]      表示4字节
	int id;
        //[8].....[15]      原则1
	double weight;
        //[16]..[19],总长要为8的整数倍,仅对齐之后总长为[0]~[19]为20,补齐[20]...[23]  原则3
	float height;
}BB;

typedef struct aa  
{  	
        //[0]...[3]          原则1 
	int  id; 
        //[8]....[15]
	double score;
        //[16],[17]
	short grade;
        //[24]......[47]   原则2(因为BB内部最大成员为double,即8的整数倍开始存储)  
	BB b;
        //[48][49]
	char name[2]; 
}AA; 

int main()
{
	cout<<sizeof(AA)<<" "<<sizeof(BB)<<endl;
	return 0;
}

输出结果:

56 24

上述结果是在没有#pragma pack(n)宏的情况下,现在来讲讲关于#pragma pack(n)

编译器中提供了#pragma pack(n)来设定变量以n字节对齐方式。//n为1,2,4,8,16...

n字节对齐就是说变量存放起始地址的偏移量有两种情况:

  • 如果n大于等于该变量占用的字节数,那么偏移量必须满足默认的对齐放方式,即该变量所占用字节数的整数倍;
  • 如果n小于该变量的类型所占用的字节数,那么偏移量为n的整数倍,不用满足默认的对齐方式。

结构体总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数否则必须是n的倍数


所以在上面的代码前加上一句#pragma pack(1)

则代码输出:

  • bb:(0~3)+(4~11)+(12~15)=16;
  • aa:(0~1)+(2~5)+(6~13)+(14~15)+(16~32)=32

也就是说,#pragma pack(1)就是没有对齐规则。

再考虑#pragma pack(4)

  • bb:(0~3)+(4~11)+(13~15)=16;
  • aa:(0~1)+(4~7)+(8~15)+(16~17)+(20~35)=36

  • 二、union共用体(联合)

    共用体表示几个变量共用一个内存位置,在不同的时间保存不同的数据类型和不同长度的变量。

    在union中,所有的共用体成员共用一个空间,并且同一时间只能储存其中一个成员变量的值。当一个共同体被声明时,编译程序自动地产生一个变量,其长度为联合中元类型(如数组,取其类型数据长度最大的变量的整数倍,且要大于等于其最大成员所占的存储空间。

    union foo
    {
       char s[10];
       int i;
    }

    在这个union中,foo的内存空间的长度为12,是int型的3倍,而并不是数组的长度10。若把int改为double,则foo的内存空间为16,是double型的两倍。

    union   mm{  
      char   a;  //元长度1     1
      int   b[5];//元长度4     20
      double   c;//元长度8     8
      int   d[3];//            12
    };  

    所以sizeof(mm) = 8 * 3 = 24(因为int b[5]最大,为20,所以取double * 3 > 20

    当在共用体中包含结构体时,如下:

    struct inner
    {
       char     c1;
       double   d;
       char     c2;
    };
     
    union data4
    {
         struct   inner t1;
         int           i;
         char        c;
     };

    由于data4共用体中有一个inner结构体,所以最大的基本数据类型为double,因此以8字节对齐。共用体的存储长度取决与t1,而t1长度为24,因此sizeof(data4)的值为24。

    当在结构体中包含共用体时,共用体在结构体里的对其地址为共用体本身内部所对齐位数。如下:

    typedef union{
    	long i;
    	int k[5];
    	char c;
    }DATE;
    
    struct data{
    	int cat;
    	char cc;
    	DATE cow;
    	char a[6];
    };
    

    sizeof(DATE) = 20,而在结构体中是4+1+3(补齐4对齐)+20+6+2(补齐4对齐)=36;


    三、二者区别

    • 共用体和结构体都是由多个不同的数据类型成员组成,但在任何同一时刻,共用体只存放了一个被选中的成员,而结构体所有成员都存在
    • 对于共用体不同成员赋值,将会对其它成员重写,原来成员的值就不存在了,而对于结构体不同成员赋值互不影响的。