结构体字节对齐

一、为什么结构体内存对齐

其实我们都知道,结构体只是一些数据的集合,它本身什么都没有。我们所谓的结构体地址,其实就是结构体第一个元素的地址。这样,如果结构体各个元素之间不存在内存对齐问题,他们都挨着排放的。对于32位机,32位编译器(这是目前常见的环境,其他环境也会有内存对齐问题),就很可能操作一个问题,就是当你想要去访问结构体中的一个数据的时候,需要你操作两次数据总线,因为这个数据卡在中间,如图:

在上图中,对于第2个short数据进行访问的时候,在32位机器上就要操作两次数据总线。这样会非常影响数据读写的效率,所以就引入了内存对齐的问题。另外一层原因是:某些硬件平台只能从规定的地址处取某些特定类型的数据,否则会抛出硬件异常。

二、结构体内存对齐的规则

下表是Windows XP/DEV-C++和Linux/GCC中基本数据类型的长度和默认对齐模数。

char short int long float double long long long double
Win-32 长度 1 2 4 4 4 8 8 8
Linux-32 长度 1 2 4 4 4 8 8 12
Linux-64 长度 1 2 4 8 4 8 8 16
MAC-64 长度 1 2 4 8 4 8 8 16

1.未指定#pragma pack时

a.第一个成员起始于0偏移处;
b.每个成员按其类型大小和指定对齐参数n中较小的一个进行对齐;
c.结构体总长度必须为所有对齐参数的整数倍,或者理解为最大类型的整数倍;
d.对于数组,可以拆开看做n个数组元素。

2.指定#pragma pack(n)时

a. n必须为0 1 2 4 8 16,其中为0时就相当于未指定#pragma pack
b. 当结构体中的类型小于n时,按该类型的对齐规则对齐,对于大于等于n的类型,按n对齐。
c.结构体总长度: 1.当结构体中的类型全都小于n时,总长度为所有对齐参数的整数倍,或者理解为最大类型的整数倍;2.当结构体中有大于等于n的类型,按n的整数倍对齐。

三、具体举例 运行环境为MAC-64

1.未指定#pragma pack时

例1
struct S1
{

    short a1;
    short a2;
    short a3;

};
struct S2
{
    long a1;
    short a2;
};

打印结果为 size of s1:6 -- size of s2:16
sizeof(S1) = 6; 这个很好理解,三个short都为2。

例2
struct S1{
  int a;
  char b;
  short c;
};

struct S2{
  char b;
  int a;
  short c;
};

打印结果:size of s1:8 -- size of s2:12


字节对齐示意图

上图中一个单元格表示一个字节 1表示占用,0表示空闲
由上图可以看出要按类型字节长度的整数倍位置对齐,其他位置被空闲。最终所占长度为最大类型的整数倍。

  1. S1中a 占4个字节。b占1个字节,c占2个字节,但是c要从2的倍数处对齐,因此,正好S1占8个字节,是所有类型的整数倍,占8个字节。
  2. S2中b占1个字节 占0位置,a 占4个字节 从索引4开始对齐,c占2个字节 从索引8开始对齐,总长为10,但是整个空间应该为所有类型的整数倍,因此需要2个空闲位。总共12个字节
例3 下面是结构体嵌套情况
struct S1{
     int a;
     double b;
     float c;
};

struct S2{
     char e[2];
     int f;
     double g;
     short h;
     struct S1 s;
};

打印结果:size of s1:24 -- size of s2:48
sizeof(S1) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个S1为24。
我们看看S2的内存布局:

e f g h s
1 1 * * 1 1 1 1 1 1 1 1 1 1 1 1 1 1 * * * * * * 1111**** 11111111 11******

s 为结构体S1 看做一个整体,他的最大类型double为8位 需要按8的倍数对齐,因此从索引24开始 因此总共48位

例4
struct S1
{
  short a;
  int b;
};

struct S2
{
    char c;
    struct S1 d;
    double e;
};

打印结果:size of s1:8 -- size of s2:24
内存布局如下:

S1 a b
1 1 * * 1 1 1 1
S2 c d e
1 * * * 11** 1111 * * * * 1 1 1 1 1 1 1 1

综上可知

在结构体嵌套的情况下 结构体看做一个整体,他按他自己的最大成员的类型进行对齐,最终按整个结构体中最大类型对齐。

指定#pragma pack(n)时(#pragma pack 是编译预处理指令,可以指定按多少字节对齐)

a. n必须为0 1 2 4 8 16,其中为0时就相当于未指定#pragma pack
b. 当结构体中的类型小于n时,按该类型的对齐规则对齐,对于大于等于n的类型,按n对齐。
c.结构体总长度: 1.当结构体中的类型全都小于n时,总长度为所有对齐参数的整数倍,或者理解为最大类型的整数倍;
2.当结构体中有大于等于n的类型,按n的整数倍对齐。

例1
#pragma pack(1)
struct S1
{
    char  a;
    short b;
    short c;

};
struct S2
{
    long d;
    short e;
    char f;
};

打印结果为:size of s1:5 -- size of s2:11
很好理解n=1时,按顺序排放

pragma pack(2) 时的结果:size of s1:6 -- size of s2:12
此时 S1中的short类型正好等于2个字节 因此按2个字节对齐,S2中long类型占8字节大于2 因此按2字节对齐

pragma pack(4) 时的结果: size of s1:6 -- size of s2:12
此时S1中的所有类型长度都小于4,因此按S1中最大类型short对齐。S2中long类型占8字节大于4 因此按4字节对齐

pragma pack(8) 时的结果:size of s1:6 -- size of s2:16
此时S1中的所有类型长度都小于8,因此按S1中最大类型short对齐。
S2中long类型占8字节正好等于8 因此按8字节对齐,因此为16字节

pragma pack(16) 时的结果:size of s1:6 -- size of s2:16
此时S1还是所有类型小于16,因此按S1中最大类型short对齐。
S2中long类型占8字节小于16 因此按S2中最大类型long 8字节对齐,因此为16字节.

例2 结构体嵌套情况
#pragma pack(1)
struct S1
{
  short a;
  int b;
};

struct S2
{
    char c;
    struct S1 d;
    double e;
};

打印结果:size of s1:6 -- size of s2:15
很好理解所有结构体顺序排放

pragma pack(2) 的时候:size of s1:6 -- size of s2:16
S1还是6字节。
S2的布局情况如下

S2 c d e
1 * 11 1111 11111111

pragma pack(4) 的时候: size of s1:8 -- size of s2:20
此时S1中的int类型正好是4 因此按4字节对齐 因此占8字节
S2的内存布局如下:

S2 c d e
1 *** 11**1111 11111111

此时因为d是S1类型 S1看做一个整体,在S1中按4字节对齐 因此从4开始布局,e由于大于pack(4) 因此按4字节对齐排序,从12索引出开始布局。因此总共20字节 也是4的整数倍。

pragma pack(8) 的时候: size of s1:8 -- size of s2:24
此时S1中的int类型小于8,因此按S1中最大类型int对齐
S2的内存布局如下:

S2 c d e
1 *** 11**1111 **** 11111111

此时S2中最大类型(double)的成员e正好等于8,因此按8字节对齐,因此从索引16出开始布局,最终为24个字节

pragma pack(16) 的时候: size of s1:8 -- size of s2:24
此时S2中最大类型(double)成员e小于16,因此按8字节对齐,因此情况和pragma pack(8) 的时候一样。

你可能感兴趣的:(结构体字节对齐)