从内存对齐来解决结构体的大小问题

今天我们来讨论一下结构的大小:关于结构的大小我们先来做两个练习。
struct stu1
{
    char ch1;
    int i;
    char ch2;
};

struct stu2
{
    char ch1;
    char ch2;
    int i;
};
    就这两个简单的练习,就能看出结构体大小似乎并不像我们所想象的那样,那么在计算结构体大小是不是有什么规律呢?在解决这个问题时我们先来补充一点有关内核在管理内存时的一些知识。

    内存对齐:内存对齐,是一种在计算机内存中排列数据(表现为变量的地址)、访问数据(表现为CPU读取数据)的一种方式,包含了两种方式:基本数据对齐和结构体数据对齐。

      也许我们都想知道为什么需要内存对齐?内存对齐有什么好处?

     解决这两个问题首先我们的知道在计算机体系中,内存中的数据是如何被读写的。计算机从内存中读取数都是按照一定固定长度读取的。例如32位计算机,每次读写内存中的数据都是以4个字节32个位为一个块(64位计算机则是按8个字节64个位为一个块)来操作。这样做不仅可以提高读取效率还能够使内存的管理变得方便并在这两个前提下最有效地利用内存空间;对于内存对齐的好处在数据的读取就能够得到体现。以32位计算机为例,读取一个数据时是以四个字节为一个块一次读取,而采用四个字节的内存对齐方式能够在最快和最节约地址空间的前提下将所需的数据取出。

例如:现有2个char类型(1个字节)和一个int类型(4个字节)根据内存对齐原则有如下3中存放形式:

       (如中间图所示)在不考虑地址对齐的情况时char类型能够一次取出,而对于int类型至少需要两次才能将数据取出。(如两边图所示)不同方式的内存对齐对空间的利用也是不同的,左图虽然在内存上做了对齐,但空间没有得到最有效利用。对于右图,不仅做了内存对齐而且还最优的利用了空间,是最优的存储方式。(在满足地址对齐的同时虽然使得一部分的地址空间被浪费,但这样却加快了数据读取速度,这是一种以空间换时间的做法。关于空间与时间的重要性不同时间不同情况是不同的,但就目前的计算机发展来看时间显然是摆在首位的。)所以在存储数据时都是以4个字节为一个单位存储(32位计算机)。那么我们就能够得出上面两个练习的答案分别是12和8。 

     内存对齐是变量在内存上存储信息的规律。那么对于结构体是不是也都像前面所说的以4个字节为一个单位进行内存对齐的呢(如上面两个例子)?答案是否定的,由于存储变量时地址对齐的要求,编译器在编译程序时对于结构体的大小会做如下处理:

    1、每个成员的偏移量都必须是当前成员所占内存大小的整数倍如果不是编译器会在成员之间加上填充字节。
    2、当所有成员大小计算完毕后,编译器判断当前结构体大小是否是结构体中最宽的成员变量大小的整数倍 如果不是会在最后一个成员后做字节填充结构体中的偏移量是一个成员的实际地址和结构体首地址之间的距离。

   

在将上面两条总结一下于是就有如下两条规则:

      1、结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)

      2、结构体大小必须是所有成员大小的整数倍。

现在我们就根据上面的两条规则来分析几个比较特殊的例子。首先我们来分析一下结构体嵌套结构体时结构体大小的计算。

struct s1

{

      char ch1;

      struct

     {

       char ch2;

       char str[10];

      }x;//注意此处得给出结构体变量名,否者只是定义了一个类型无法计算大小

};

对于结构体s1的大小正确结果为12,并不是我们所想的22,这是由于结构体x中的成员也是结构体s1的成员并且是单独的个体。数组元素也是单独的个体,在确定对齐大小时不能以结构体x的大小和整个数组的大小为对齐标准,应以数组元素类型为标准计算结构体大小。此时结果为10*1+1*1+1*1=12,而不是(10+1)*2=22。所以此处的对齐标准为char类型。在将str改为int型,这时候s1的大小就变成了10*4+1*4+1*4=48。再例如下面这种情况:

struct s2

{

      char ch1;

      struct

     {

      int m;

      char str[10];

     }x;

};

此时的结果就变成了1*4+1*4+3*4=20;当我们将ch1和m做调换是结果就变成了1*4+3*4=16。 (10个char需要3个4字节空间,11个char时3个4字节空间也能存放)。当结构体中的成员都是以单独的情况出现,即没有没有以数组一类的形式出现时计算就变得相对简单,根据上面的两原则就可计算出结果。例如:

struct s2

{

      char ch1;

      int m;

      double n;

};

根据以上两条原则结果就为1*8+1*8=16。

刚才是结构体的嵌套,还有另外一种情况就是结构体中存在联合体时结构体大小的计算,例如

struct s1                     struct s2

{                              {

      char ch1;                      char ch1;

      union                          union

     {                               {

        char ch2;                       int x;

        char str[10];                   char str[10];

     };                              };

};                            };


 

 struct s3                      struct s4

{                              {

      int x;                          char ch1;

      union                          union

      {                               {

        char ch2;                       char ch2;

        char str[10];                   int x[10];

       };                              };

};                            };

在计算这四个结构体的大小时我们要结合联合体的大小和和寻找结构体对齐大小的原则来解决。我们能够计算出s1的大小为1*1+10*1=11,s2的大小为1*4+3*4=16,s3的大小为1*4+3*4=16,s4的大小为1*4+10*4=44。

此外,在计算结构体大小时还存在一种给定对齐大小的情况,给定结构体对齐大小通过#pragma  pack(x)来指定x为对齐大小。但在给定x赋值时出现了这样一条警告:

 warning C4086: 杂注参数应为“12”、“4”、“8”或者“16

经过多次验证发现在给定参数x为上面的几个值时结构体对齐大小遵循一下两条规律:

1、指定对齐大小 < 结构体自身对齐大小   以指定对齐大小为准

2、指定对齐大小 > 结构体自身对齐大小   以结构体自身对齐大小为准

但是当参数x不为以上的几个值时编译器做了以下处理(vs2008):

只要指定对齐大小不为“1”、“2”、“4”、“8”、“16”计算结构体大小时都是以结构体自身对齐大小为准。

例如:

#pragma pack(x)

struct   stu

{

       int x;

       char ch1;

       double y;

};

当x的值为1、2、4、8、16时结构体stu的大小分别为13、14、16、16、16,当x的值不是这几个值时无论x为多少结构体stu的大小都为16。

猜你喜欢

转载自blog.csdn.net/magic_world_wow/article/details/79625043