结构体的内存对齐

1、为何要内存对齐

为了高速处理数据,现代处理器的设计引入了对齐的概念。所谓对齐就是保证数据在内存中存储时地址变化按照必定的规律,这样就能够保证每次cpu取一样长度的数据进行运算,所以能够提升计算机的运行速度。spa

2、什么是内存对齐

下面的程序演示内存中的对齐:设计

#include <stdio.h>

struct test{
  char ch;
  short s;
  int i;
};

int main(void)
{
  struct test var;
  printf("size of var : %d\n", sizeof(var));
  return 0;
}

运行结果为:code

该结构体拥有一个char型、一个short型和一个int型成员变量,所占用的字节数应该是7个,可是结果倒是8个字节,这是由于编译器采用了默认的对齐方式。内存

3、内存对齐的一些概念

1.数据类型自身的对齐值

基本数据类型的自身对齐值为其数据类型的大小。编译器

2.指定对齐值

默认的指定对齐值为成员中自身对齐值最大的那个值。io

使用下面语句能够指定对齐值value:编译

#progma pack (value) /*指定按value字节对齐*/
struct A {
    int a;
};
#progma pack () /*取消指定对齐,恢复缺省对齐*/

3.结构体或者类的自身对齐值

其成员中自身对齐值最大的那个值。class

4.数据类型、结构体和类的有效对齐值

自身对齐值和指定对齐值中较小的那个值。test

4、内存对齐的实现

设结构体以下:变量

struct test{
  char ch;
  int i;
  short s; 
};

假设test的起始地址为0x0000,默认的指定对齐值为4,因此:

  1. 第一个成员变量ch,自身对齐值为1,小于指定对齐值4,因此有效对齐值为1,ch的存放地址为0x0000,而且0x0000是自身对齐值1的倍数。
  2. 第二个成员变量i,自身对齐值是4,等于指定对齐值4,因此有效对齐值为4,而变量起始地址应为自身有效对齐值的倍数,因此i应存放在0x0004到0x0007这四个连续的字节空间。
  3. 第三个成员变量s,自身对齐值为2,小于指定对齐值为4,因此有效对齐值为2,变量起始存放地址应为2的倍数,因此s存放在0x0008到0x0009两个字节空间。
  4. 接下来看结构体test自身对齐值为其变量中最大对齐值(这里是i)因此就是4,因此结构体的有效对齐值也是4,结构体的大小应为结构体有效对齐值的倍数,因此因此0x0000A到0x000B也被结构体所占用,0x0000到0x000B共12个字节,这就是结构体test所占内存大小。

当咱们指定对齐值为2字节时,再分析上面的结构体

#pragma pack(2)  //指定按2字节对齐
struct test1{
  char ch;
  int i;
  short s; 
};
#pragma pack()  //取消指定对齐,恢复缺省对齐

 假设test1的起始地址为0x0000,指定对齐值为2,因此:

  1. 第一个成员变量ch,自身对齐值为1,小于指定对齐值为2,因此有效对齐值为1,ch存放在0x0000。
  2. 第二个成员变量i,自身对齐值为4,大于指定对齐值,因此有效对齐值为2,存放的首地址为有效对齐值2的倍数即0x0002,所以i存放在0x0002,0x0003,0x0004,0x0005四个连续的字节空间。
  3. 第三个成员变量s,自身对齐值为2,等于指定对齐值,因此有效对齐值为2,存放首地址为2的倍数即0x0006,所以s存放在0x0006,0x0007两个字节空间。
  4. 结构体test1自身对齐值即最大成员变量自身对齐值4,大于指定对齐值2,因此有效对齐值为2,而结构体大小为0x0000到0x0007共8个字节,是2的倍数,所以结构体test1的内存大小为8字节

5、分析几个程序

1.程序以下:

#include <stdio.h>

#pragma pack(8)
struct example1
{
  short a;
  long b;
};
struct example2
{
  char c;
  struct example1 struct1;
  short e;
};
#pragma pack()

int main(int argc, char* argv[])
{
  struct example2 struct2;
  struct example1 e1;
  printf("size of example1 : %d\n", sizeof(e1));
  printf("size of example2 : %d\n", sizeof(struct2));
  printf("%d\n", (unsigned long int)(&struct2.struct1) - (unsigned long int)(&struct2));

  return 0;
}

程序的输出结果是什么?

答案是:

分析这个程序:

对于example1:

  1. 成员变量a,自身对齐值为2,指定对齐值为8,因此有效对齐值为2,所以a占2个字节空间。
  2. 成员变量b,自身对齐值为8,等于指定对齐值,因此有效对齐值为8,起始地址应为8的倍数,所以须要填充6个字节,b占8个字节空间。
  3. 结构体的有效对齐值为8,而成员变量共占16个字节,是8的倍数,所以结构体example1共占16个字节空间。

对于example2:

  1. 成员变量c,自身对齐值为1,小于指定对齐值,因此有效对齐值为1,占1个字节空间。
  2. 成员struct1是一个example1结构体,该结构体的自身对齐值为8,等于指定对齐值,因此起始地址为8的倍数,所以填充7个字节,占16个字节空间。
  3. 成员e,自身对齐值2,小于指定对齐值,有效对齐值为2,占两个字节大小。
  4. 结构体多有成员所占大小为:1+7+16+2=26,而结构体的有效对齐值为8,所以结构体需占32个字节空间(8的倍数)。 

 对于(unsigned long int)(&struct2.struct1) - (unsigned long int)(&struct2):

由于在example2中struct1以前有个char类型的成员变量,因为struct1的对齐值为8因此char变量须要填充7个字节,所以结果为8。

2.第二个程序:

#include <stdio.h>\

union A
{
  int a[5];
  char b;
  double c;
};

struct B
{
  int n;
  union A a;
  char c[10];
};

int main(void)
{
  union A a1;
  struct B b;
  printf("size of union : %d\n", sizeof(a1));
  printf("size of struct : %d\n", sizeof(b));
  return 0;
}

A和B的大小为:

分析程序:

因为union是共享内存的,首先看每一个成员所占大小:

union A
{
  int a[5];  //20字节
  char b;    //1字节
  double c;  //8字节
};

A中各变量默认内存对齐方式,必须以最长的double 8字节对齐,因此A的大小为24字节(20+4)。

结构体B中:

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

因为A是8字节对齐的,因此B中的int与char[]也须要8字节对齐,因此大小为:8+24+16 = 48。

相关文章
相关标签/搜索