结构体内存对齐,结构体变量大小计算,位段内存分配及相关问题

目录

1.结构体

1.1计算

结构体对齐规则

结构体大小计算规则

1.2 为什么存在内存对齐?

1.3设计优化

1.4修改默认对齐数

 2.位段

2.1什么是位段

2.2位段的内存分配

2.2.1大小计算

2.2.2内存分配

 2.3位段的跨平台问题


1.结构体

1.1计算

 在学习结构体变量大小计算前,首先要掌握结构体对齐规则

结构体对齐规则

  1. 第一个成员在结构体变量偏移量为0;
  2. 其他成员要对齐到某个数(对齐数)的整数倍的地址处(此处的地址设首地址为0,此后每个字节递加1)
  • 对齐数:编译器默认的一个对齐数 与 该成员大小的较小值。
  • VS的默认值为8

结构体大小计算规则

  1. 结构体总大小为最大对齐数(每一个成员都有一个对齐数)的整数倍;
  2. 如果嵌套了结构体的的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍地址处,结构体整体大小就是最大对齐数(含嵌套的结构体)的整数倍地址处。

小结

因此计算一个结构体的大小可大致分为2步

  1. 把每一个成员按照对齐方式放在内存格上(第一个成员从0开始,之后的元素放在紧接着的整数倍地址处),写出对齐数。
  2. 结构体总大小(内存格的总长度)是最大的对齐数的整数倍

整个过程中间可以有浪费的格。

练习

//练习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什么是位段

与结构体声明类似,有两个不同:

  1. 位段成员必须是char, int, unsigned int, signed int.
  2. 位段成员后面有一个冒号和一个数字。

比如:

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));
}
  1. 首先根据int 分配4个字节——32bit,将_a, _b 逐个放入,当放到_c的时候32位放不下,就再申请32位
  2. 这时有两种方案:

                一 . 跳过剩下的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;
}

 

  1.  a=10转换为二进制是1010,但只分配了3位,所以将进行截断,剩010
  2. vs是从右向左放的,放入后是00000010,
  3. 之后在左边放b
  4. 重新开辟1字节放c,按此方法放入d,如上图。

内存变化:

 

 2.3位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

总结:

跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值