c——复合类型——struct union 内存对齐

时间:2022-09-05 14:34:03

struct vs array

  • 数组是相同数据类型的聚集体,通过相对于数组首地址的offset获取数组元素,数组每个成员保存了相对于数组首地址的offset和自身字长
  • struct是不同数据类型的聚集体,通过相对于struct首地址的offset获取struct成员,struct每个成员保存了相对于struct首地址的offset和自身字长
注:数组可理解为特殊的struct,成员类型相同(字长相同)的struct

内存对齐

#pragma pack(4)

struct EmptyAnimal
{
};

struct Animal1
{
int food[2];
short age;
long color;
char name[5];
};

struct Animal2
{
long color;
int food[2];
char name[5];
short age;
};

void mem_align()
{
struct Animal1 animal1;
struct Animal2 animal2;

printf("-----sizeof-----\n");
printf("sizeof(food) = %ld\n", sizeof(int[2]));
printf("sizeof(age) = %ld\n", sizeof(short));
printf("sizeof(color) = %ld\n", sizeof(long));
printf("sizeof(gender) = %ld\n", sizeof(char[5]));

printf("-----EmptyAnimal-----\n");
printf("sizeof(struct EmptyAnimal) = %ld\n", sizeof(struct EmptyAnimal));

printf("-----Animal1-----\n");
printf("sizeof(struct Animal1) = %ld\n", sizeof(struct Animal1));
printf("food offset = %ld\n", (char*)&animal1.food - (char*)&animal1);
printf("age offset = %ld\n", (char*)&animal1.age - (char*)&animal1);
printf("color offset = %ld\n", (char*)&animal1.color - (char*)&animal1);
printf("name offset = %ld\n", (char*)&animal1.name - (char*)&animal1);

printf("-----Animal2-----\n");
printf("sizeof(struct Animal2) = %ld\n", sizeof(struct Animal2));
printf("color offset = %ld\n", (char*)&animal2.color - (char*)&animal2);
printf("food offset = %ld\n", (char*)&animal2.food - (char*)&animal2);
printf("name offset = %ld\n", (char*)&animal2.name - (char*)&animal2);
printf("age offset = %ld\n", (char*)&animal2.age - (char*)&animal2);
}
output:
-----sizeof-----
sizeof(food) = 8
sizeof(age) = 2
sizeof(color) = 8
sizeof(gender) = 5
-----EmptyAnimal-----
sizeof(struct EmptyAnimal) = 0
-----Animal1-----
sizeof(struct Animal1) = 28
food offset = 0
age offset = 8
color offset = 12
name offset = 20
-----Animal2-----
sizeof(struct Animal2) = 24
color offset = 0
food offset = 8
name offset = 16
age offset = 22
注:empty struct的sizeof为1(非0最小值),若为0,empty struct对象不分配内存,就没有相应地址了,显然不符逻辑 struct内存对齐规则:
  • struct成员对齐:align_offset = min(pack(n), 成员对齐字长),确保struct成员的offset被align_offset整除,如果struct成员为数组类型,成员对齐字长为数组元素类型对齐字长,如果struct成员为struct,成员对齐字长为struct对齐字长,其余类型对齐字长为类型的sizeof值
  • struct对齐字长:align_size = min(pack(n), 成员最大对齐字长),当struct作为其他struct成员或struct数组元素时,align_size即为struct类型对齐字长
注:#pragma pack(n)设置n字节对齐,n可取1,2,4,8等,pack默认取4 当#pragma pack(4)时,根据struct内存对齐规则
  • animal1的内存布局:FFFF FFFF AAPP CCCC CCCC NNNN NPPP(0:8:12:20)
  • animal2的内存布局:CCCC CCCC FFFF FFFF NNNN NPAA(0:8:16:22)
假设#pragma pack(2),根据struct内存对齐规则
  • animal1的内存布局:FFFF FFFF AACC CCCC CCNN NNNP(0:8:10:18)
  • animal2的内存布局:CCCC CCCC FFFF FFFF NNNN NPAA(0:8:16:22)
假设#pragma pack(8),根据struct内存对齐规则
  • animal1的内存布局:FFFF FFFF AAPP PPPP CCCC CCCC NNNN NPPP(0:8:16:24)
  • animal2的内存布局:CCCC CCCC FFFF FFFF NNNN NPAA(0:8:16:22)
注:F = food,A = age,C = color,N = name,P = padding

内存对齐作用

主要作用:
  • 平台原因(移植原因),不是所有硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
  • 性能原因,经过内存对齐后,CPU的内存访问速度大大提升
内存空间是线性的,由一个个的字节组成,如下图: c——复合类型——struct union 内存对齐但CPU并不是这么看待的,CPU把内存当成是以块为单位的,块的大小可以是1,2,4,8,16字节,因此CPU在读取内存时以块大小进行读取,块大小称为memory access granularity,即内存读取粒度,如下图: c——复合类型——struct union 内存对齐假设CPU要读取一个int型4字节的数据到寄存器,分两种情况讨论:
  • 数据从0字节开始
  • 数据从1字节开始
注:假设内存读取粒度为4 当该数据是从0字节开始时,CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中 c——复合类型——struct union 内存对齐当该数据是从1字节开始时,此时该int型数据不是位于内存读取边界上,即内存未对齐数据,此时CPU先访问一次内存,读取0-3字节的数据进寄存器,并再次读取4-7字节的数据进寄存器,接着把0,5,6,7字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器,显然,对一个内存未对齐数据进行了许多额外的操作,大大降低了CPU性能 c——复合类型——struct union 内存对齐注:内存对齐的作用之一为平台移植,因为上述操作只有部分CPU支持,一部分CPU遇到未对齐边界就直接罢工了

union

从struct角度理解union,union是一种特殊的struct:
  • union是所有成员相对于首地址的offset均为0的struct
union内存对齐规则:
  • union成员对齐:union所有成员相对于首地址的offset均为0,0可被任何数整除,因此union成员自然对齐
  • union对齐字长:align_size = min(pack(n), 成员最大对齐字长),当union作为struct(union)成员或union数组元素时,align_size即为union类型对齐字长