结构体内存对齐
如何计算结构体的大小?
首先得掌握结构体的对齐规则:
1.第一个成员在与结构体变量偏移量为0的地址处。(将第一个成员放在结构体内存的第0处)
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。(从0地址处开始,偏移量逐渐增减,每个字节加一,第二个成员开始,将成员的字节大小跟对齐数(VS2019编译器的默认对齐数是 8)进行比较,选两者中较小的,把该变量放到小的对齐数的偏移量地址上)
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
- VS中默认的对齐数为8
- Linux - 没有默认对齐数的概念
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。(如果全部成员自身的的大小已经放入内存,但最后一个成员的尾字节所在的偏移量不是最大对其数的整数倍,则在最后要浪费空间直到刚好到整数倍为止)
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
struct S
{ //类型大小 VS默认对齐数 两者中小的对齐数
char c1; // 1 8 1
int i; // 4 8 4
double d; // 8 8 8
};
int main()
{
struct S s;
printf("%d\n",sizeof(s)); //结构体大小为16,并不是简单的变量大小相加
}
为什么存在内存对齐?
大部分的参考资料都是这样说的:
1.平台原因(移植原因)︰不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
⒉.性能原因︰数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说︰
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到∶
1.让占用空间小的成员尽量集中在一起。
2.修改默认对齐数
之前我们见过了#pragma这个预处理指令,这里我们再次使用,可以改变我们的默认对齐数。
#pragma pack(2) //设置默认对齐数为2
struct s1
{
char c1;
int i;
char c2;
};
#pragma pack() //取消设置的默认对齐数,还原为默认
此时结构体大小为8,如果设置默认对齐数为1的话,则成员紧挨着存放,等于没有对齐
offsetof宏:
用于计算结构体中某变量相对于首地址的偏移量(以字节为单位)
函数原型:offsetof (type,member)
使用:
#include <stdio.h>
#include <stddef.h> //需要引入头文件
struct s1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", offsetof(struct s1, c1)); //0,C1在偏移量为0的地址处
printf("%d\n", offsetof(struct s1, i)); //4,i在偏移量为4的地址处
printf("%d\n", offsetof(struct s1, c2)); //8,C2在偏移量为8的地址处
return 0;
}
模拟实现offsetof宏:
#include <stdio.h>
struct A
{
char a;
int b;
char c;
double d;
};
#define OFFSETOF(struct_name,mem_name) (int)&(((struct_name *)0)->mem_name)
int main()
{
//模拟实现宏offsetof
printf("%d\n", OFFSETOF(struct A, a));
printf("%d\n", OFFSETOF(struct A, b));
printf("%d\n", OFFSETOF(struct A, c));
printf("%d\n", OFFSETOF(struct A, d));
return 0;
}
解释:定义宏时,将0强制转换为结构体类型,只是0不再是个整型,而是个地址,是结构体的首地址,并没有真正去创建结构体变量,然后通过->找到相应的结构体变量,&取出该变量的地址,减去0地址,再强制类型转换为int型,就能输出变量相对于首地址的偏移量,这里没有减-0,因为没有意义,如果规定首地址是0x10,那最后强转为int之前就要减去首地址0x10,才是偏移量