欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

结构体的内存对齐

程序员文章站 2024-03-23 10:31:46
...

一、为什么要内存对齐

为了高速处理数据,现代处理器的设计引入了对齐的概念。所谓对齐就是保证数据在内存中存储时地址变化按照一定的规律,这样就可以保证每次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。

 

转载于:https://my.oschina.net/daowuming/blog/769781