结构体内存对齐:
计算结构体的大小
结构体成员不是按照顺序在内存中连续存放的而是有一定的对齐规则的
结构体内存对齐的规则:
1、结构体的第一个成员永远放在相比于结构体变量起始位置的偏移量为0的位置。
2、从第二个成员开始,往后的每个成员都要对齐到某个对齐数的整数倍处。对齐数:结构体成员自身的大小和默认对齐数的较小值。在VS环境上的默认对齐数是8,在gcc(Linux)环境上没有默认对齐数,对齐数就是结构体成员的自身大小。
3、结构体的总大小必须是最大对齐数的整数倍(最大对齐数是所有成员的对齐数中的最大的值)
4、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
offsetof
对应的头文件是:#include <stddef.h>
可以计算结构体成员相较于结构体起始位置的偏移量
#include <stddef.h>
#include <stdio.h>
struct S1
{
char c1;//1
int i;//4
char c2;//1
};
//0 - 1
//1 2 3 (4 5 6 7) - 4
//8 - 1
//0 - 8 - 9个空间单元 但是不是4的整数倍
//10 11 12是4的整数倍
//12
struct S2
{
int i;//4
char c1;//1
char c2;//1
};
//0 1 2 3 - 4
//4 - 1
//5 - 1
//0 - 5 6个存储空间单元 但是不是4的整数倍
//7 8是4的整数倍
//8
int main()
{
struct S1 s1 = { 0 };
printf("%d\n", sizeof(struct S1));//12
printf("%d\n", sizeof(struct S2));//8
printf("%d\n", offsetof(struct S1, c1));//可以计算结构体成员相较于结构体起始位置的偏移量
printf("%d\n", offsetof(struct S1, i));
printf("%d\n", offsetof(struct S1, c2));
printf("%d\n", offsetof(struct S2, i));
printf("%d\n", offsetof(struct S2, c1));
printf("%d\n", offsetof(struct S2, c2));
return 0;
}
#include <stdio.h>
struct S3
{
double d;
char c;
int i;
};
//0 - 7 - 8个
//8 - 1个
//9 10 11 (12 13 14 15) - 4个
//0 - 15 是16个空间刚好是8的整数倍
//16
struct S4
{
char c1; // 1
struct S3 s3; // 8 大小空间为16
double d; // 8
};
//0 - 1个位置
//第二个是结构体:其对应的最大对齐数是8,移到8的位置开始
//这个结构体的总共大小为16,所以从8开始到23放这个结构体
//然后24开始到31存放第三个元素
//总共0-31是32个空间大小
//32 刚好32也是8的整数倍
int main()
{
printf("%d\n", sizeof(struct S3));
printf("%d\n", sizeof(double));//8
printf("%d\n", sizeof(float));//4
printf("%d\n", sizeof(struct S4));//32
return 0;
}
如果说遇到数组作为结构体的元素:
int a[3];可以直接看作3个整型元素存放进去
总体来说:结构体的内存对齐是拿空间来换取时间
我们可以通过让占用空间小的成员尽量集中在一起
struct S1
{
char c1;//1
int i;//4
char c2;//1
};//12
struct S2
{
int i;//4
char c1;//1
char c2;//1
};//8
修改默认对齐数:
使用 #pragma
#pragma pack(8) //设置默认对齐数为8
#pragma pack() //取消设置的默认对齐数,还原为默认
#pragma pack(1)
//默认对齐数为1
struct S
{
char c1;//1 1 1
int a; // 4 1 1
char c2;//1 1 1
};
#pragma pack()
int main()
{
printf("%d\n", sizeof(struct S));//6
return 0;
}
位段
1、位段的成员必须是:int、unsigned int 或者 signed int(char也是属于整型家族的)
2、位段的成员名后边有一个冒号和一个数字
struct A
{
int _a : 2;//二进制位
int _b : 5;
int _c : 10;
int _d : 30;
//总共需要47个二进制位
//1个int是32位
//2个int是8个字节
};
int main()
{
printf("%d\n", sizeof(struct A));//8
return 0;
}
struct S
{
char a : 3;
//3个比特位 先给你一个字节
char b : 4;
//4个比特位
char c : 5;
//5个比特位 需要再去申请一个字节
char d : 4;
//4个比特位 需要再去申请一个字节
};
int main()
{
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
printf("%d\n", sizeof(s));//3
return 0;
}
枚举enum
联合(共用体)union
联合的成员是共用同一块内存空间的,这样的一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
联合体大小的计算:联合体的大小至少是最大成员的大小
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
//判断大小端存储方式
int check_sys()
{
union
{
int i;
char c;
//使用同一块空间
}un = {.i = 1};
return un.c;
//01 00 00 00 小端
//00 00 00 01 大端
}
int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");//小端存储方式
else
printf("大端\n");
return 0;
}
union Un1
{
char c[5];//(5) 1 8 (1)
int i;//(4) 8 (4)
};
//5 为了凑成4的整数倍 所以为8
union Un2
{
short c[7];//(14) 2 8 (2)
int i;//(4) 4 8 (4)
};
//14为了凑成4的整数倍 所以为16
int main()
{
printf("%d\n", sizeof(union Un1));//5+3 = 8
printf("%d\n", sizeof(union Un2));//16
return 0;
}