【C++ Primer】 神秘的 sizeof(union) 、sizeof(struct) 和内存对齐技术

时间:2022-09-05 11:46:03

一,union:C/C++关键字  共用体(联合)

       共用体的声明和共用体变量定义与结构十分相似。形式为:

 

union 共用体名
{   
	数据类型 成员名;   
	数据类型 成员名;   
	...   
} 变量名; 


       共用体表示几个变量共用一个内存位置,在不同的时间保存不同的数据类型和不同长度的变量.在union中,所有的共用体成员共用一个空间,并且同一时间只能储存其中一个成员变量的值。当一个共用体被声明时, 编译程序自动地产生一个变量, 其长度为联合中最大的变量长度的整数倍(特别注意数组)

例子一:

 

union foo{

  int i;

  char c;

  double k;

  };


sizeof(foo);   //double最长占用8字节,所以union foo大小为8字节

 

例子二: 

union A  
{
    int a[5];       //20
    short b;     //2
    double c;    //8
    char p2;     //1
};

struct B { 
	int n; // 4字节 
	A a; // 24字节 
	char c[10]; // 10字节 
};


 

 

      sizeof(A) ;  //24     而不是20     ???

      sizeof(B) ;  //48     而不是???

      对齐: 分配内存时,每个成员放在长度倍数位置,如果不够,补位对齐

      补齐: 对整个结构变量的空间要求总长度一定是最长的成员的倍数,不够补齐不管是对齐还是补齐,最长的成员长度超过4时,以4计。

 

      A实际占用内存大小为 20字节,但是要跟 8个字节的变量double的整数倍,对齐所以为 24;

      由于A实际占用24字节,则可以想象B实际占用38字节,但A是8字节对齐的,所以int   n和char   c[10]也需要8字节对齐,总共8+24+16=48 字节。

      

例子三:

 

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


      sizeof(f);  //  12
      解释:在这个union中,foo的内存空间的长度为12,是int型的3倍,而并不是数组的长度10。
                 若把int改为double,则foo的内存空间为16,是double型的两倍。

二,struct

        具体说明见内存对齐例子

 

三,内存对齐

       1)概念:“内存对齐”应该是编译器的“管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上。

       2)原因:

            1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。   

            2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因是:为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

       3)对齐规则

      每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。   

           规则:   

          1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。   

          2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。   

         3、结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

 

pragma  pack(1) 时候

 

        1>数据成员对齐:

#pragma pack(1)
struct test_t 
{
	int a;      /* int型,  长度4 > 1 按1对齐;起始offset=0 0%1=0;存放位置区间[0,3] */
	char b;     /* char型, 长度1 = 1 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */
	short c;    /* short型,长度2 > 1 按1对齐;起始offset=5 5%1=0;存放位置区间[5,6] */
	char d[6];  /* char型, 长度1 = 1 按1对齐;起始offset=7 7%1=0;存放位置区间[7,C] */
};/*char d[6]要看成6个char型变量*/


        sizeof(test_t) ;     //输出为13

        2>整体对齐

       整体对齐系数 = min((max(int,short,char), 1) = 1

       整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 13   /*13%1=0*/


 

pragma pack(2) 时候

       1>成员数据对齐

  

  #pragma pack(2) 

  struct test_t { 

      int a; /* int型,长度4 > 2 按2对齐;起始offset=0 0%2=0;存放位置区间[0,3] */ 

      char b; /* char型,长度1 < 2 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */ 

     short c; /* short型,长度2 = 2 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */ 

      char d[6]; /* char型,长度1 < 2 按1对齐;起始offset=8 8%1=0;存放位置区间[8,D] */ 

  }; 
 

  成员总大小=14

  2> 整体对齐

  整体对齐系数 = min((max(int,short,char), 2) = 2

  整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 14 /* 14%2=0 */

 

四,终极例子

 

入门例子一:

union A
{
	int t;   //4
	short m; //2
	char p;  //1
};
	
struct B  
{
    A a;          //4
    double c;    //8
    char p2;     //1
};


       sizeof(B);  // 24 

       B中采用double长度  8字节对齐方式,所以A在结构体B内 变成8字节。实际B占用 8+8+1=17。再填充成8的整数倍后位 24字节 

 

变态例子二:

struct B  
{
    union A
	{
		int t;   //4
		short m; //2
		char p;  //1
	} ;
    double c;    //8
	char p2;     //1
};

 

       sizeof(B);  //16

 

如果你的第一反应是24那么你就躺着中枪了。想想为什么吧!!

4+8+1=13   最大类型 8的最小倍数为 16

附件:在GCC中示例代码

#include "stdio.h"

typedef struct  BB 
{  
     union AA  //没有typedef
    {  
        int t;   //4  
        short m; //2  
        char p;  //1  
    } AA;  
    double c;    //8  
    char p2;     //1  
}BB; 

  typedef union  A //有typedef
    {  
        int t;   //4  
        short m; //2  
        char p;  //1  
    }A;  
          
 typedef   struct   B  
    {  
        A a;          //4  
        double c;    //8  
        char p2;     //1  
    }B;  
    
    
  int main()
  {
      printf("BB:%d\n",sizeof(BB));
      printf("B:%d\n",sizeof(B));
      return 0;
  }