目录
1.结构体
1.1计算
在学习结构体变量大小计算前,首先要掌握结构体对齐规则
结构体对齐规则
- 第一个成员在结构体变量偏移量为0;
- 其他成员要对齐到某个数(对齐数)的整数倍的地址处(此处的地址设首地址为0,此后每个字节递加1)
- 对齐数:编译器默认的一个对齐数 与 该成员大小的较小值。
- VS的默认值为8
结构体大小计算规则
- 结构体总大小为最大对齐数(每一个成员都有一个对齐数)的整数倍;
- 如果嵌套了结构体的的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍地址处,结构体整体大小就是最大对齐数(含嵌套的结构体)的整数倍地址处。
小结
因此计算一个结构体的大小可大致分为2步
- 把每一个成员按照对齐方式放在内存格上(第一个成员从0开始,之后的元素放在紧接着的整数倍地址处),写出对齐数。
- 结构体总大小(内存格的总长度)是最大的对齐数的整数倍
整个过程中间可以有浪费的格。
练习
//练习1
struct S1
{
char c1;
int i;
char c2;
};
//练习2
struct S2
{
char c1;
char c2;
int i;
};
//练习3
struct S3
{
double d;
char c;
int i;
};
//练习4-结构体嵌套问题
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
printf("%d\n", sizeof(struct S3));
printf("%d\n", sizeof(struct S4));
}
- 练习1
第1步:放入成员,写对齐数 第2步:计算总大小,总大小为4的倍数
共12字节。
- 练习2
1.c1,c2放于0,1处,int i放于4处;
2.已占8字节,恰好为4的倍数,所以大小为8.
- 练习3
1.double d 对齐到0处占8字节,char c 放于8处,int i 放12处占4字节;
2.已占16字节,恰好是最大对齐数8的倍数,所以大小为16.
- 练习4
1.char c1 对齐到0处占1字节,struct S3 s3自己的最大对齐数是8,s3对齐到8处占16字节,double d对齐到24处占8字节
2,已占32字节,恰好是最大对齐数8的倍数,所以大小为32.
运行结果
1.2 为什么存在内存对齐?
1.平台原因(移植原因):
不是所有的硬件都能访问任意地址上的数据,某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2.性能原因:
数据结构(尤其是栈)应该尽可能在自然边界对齐。(32位4字节,64位8字节)
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是哪空间来换取时间。
1.3设计优化
根据练习1和练习2的结果,同样的内容,所占空间大小却不相同。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
- 让占用空间小的成员尽量集中在一起
例如:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
应尽可能选择struct S2的方式
1.4修改默认对齐数
#pragma pack()
#pragma pack(8)//默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
- 在对齐方式不合适的时候可以自己更改默认对齐数
2.位段
结构体讲完就得讲讲结构体实现位段的能力。
2.1什么是位段
与结构体声明类似,有两个不同:
- 位段成员必须是char, int, unsigned int, signed int.
- 位段成员后面有一个冒号和一个数字。
比如:
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
2.2位段的内存分配
- 位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
- 冒号后面的数字代表,为此成员将分配的二级制位。
2.2.1大小计算
struct A
{
int _a : 2;
int _b : 5;
int _c : 30;
int _d : 10;
};
int main()
{
printf("%d", sizeof(struct A));
}
- 首先根据int 分配4个字节——32bit,将_a, _b 逐个放入,当放到_c的时候32位放不下,就再申请32位
- 这时有两种方案:
一 . 跳过剩下的15位,直接用新开辟的32位放入_c,剩余2位,不够放_d,还需申请32位放_d,共申请了12字节。
二 . 先用剩下的15位,再用从新开辟的32位放15位,_c放完还剩17位放_d,共申请了8字节。
由于c标准没有规定用哪种方案,所以不同操作平台得出的结果是不同的。
可以看出vs平台用的是第一种方案。
2.2.2内存分配
这里我们分析VS的分配方式
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
return 0;
}
- a=10转换为二进制是1010,但只分配了3位,所以将进行截断,剩010
- vs是从右向左放的,放入后是00000010,
- 之后在左边放b
- 重新开辟1字节放c,按此方法放入d,如上图。
内存变化:
2.3位段的跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。