结构体的内存对齐

原创
2016/10/23 15:12
阅读数 229

一、为什么要内存对齐

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

二、什么是内存对齐

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

#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;
}

运行结果为:

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

三、内存对齐的一些概念

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

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

2.指定对齐值

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

使用下面语句可以指定对齐值value:

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

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

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

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字节

五、分析几个程序

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。

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部