主页 > 游戏开发  > 

计算结构体的大小(结构体内存对齐)、结构体实现位段

计算结构体的大小(结构体内存对齐)、结构体实现位段

目录 结构体内存对齐知识引入对齐规则为什么存在内存对齐修改默认对齐数 结构体实现位段什么是位段位段的内存分配位段的跨平台问题位段使用的注意事项

结构体内存对齐 知识引入

为什么两个结构体的元素相同只是位置不同,计算出的在内存中的大小就不同呢? 这是因为结构体在内存中有自己的对齐规则

对齐规则

1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处 2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处 对齐数=编译器默认的一个对齐数与该成员变量大小的较小值 VS中默认对齐数为8 Linux中gcc没有默认对齐数,对齐数就是成员自身的大小 3.结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍 4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处 结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

直接看这些概念还是很蒙的,我们还是通过代码来解释 首先介绍一个宏

offsetof – 宏 可以计算出结构体成员相较于起始位置的偏移量 offsetof (type,member) 头文件stddef.h 这样我们就知道了每个元素相对于起始位置的偏移量 现在我们通过画图来表示每个元素在结构体中的位置 我们发现即使把元素放在对应的偏移量处也才9个字节啊,为什么算出来的是12呢? 这就要用到我们的对齐规则了 到了这里你应该对结构体内存对齐有点想法了,趁热打铁,再看几道例题

//练习2 struct S1 { 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("%zd\n", sizeof(struct S1)); printf("%zd\n", sizeof(struct S3)); printf("%zd\n", sizeof(struct S4)); return 0; }

大家算出来了嘛,这里为了让大家理解,每道题我都把它的图画出来 相信到了这里你一定完全掌握对齐规则了 那么为什么存在内存对齐呢? 我们直接一个字节挨着一个字节存不好吗?为什么弄得这么麻烦?

为什么存在内存对齐

⼤部分的参考资料都是这样说的:

1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定 类型的数据,否则抛出硬件异常。

2. 性能原因: 数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要 作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地 址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以 ⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两 个8字节内存块中。

总体来说:结构体的内存对⻬是拿空间来换取时间的做法。

那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间,如何做到呢?

让占⽤空间⼩的成员尽量集中在⼀起

这个想必我们上面写的结构体S1和S2大家已经深有体会了

修改默认对齐数

#pragma这个预处理指令,可以改变编译器的默认对齐数

#pragma pack(1)//设置对齐数为1 struct S { char c1; int a; char c2; }; #pragma pack()//取消设置的对齐数,还原为默认 int main() { printf("%zd\n", sizeof(struct S)); }

这里结构体的大小是多少呢?

我们还是画图说明 实际上这样算是直接取消对齐了,因为每个元素都是紧挨着放的

确实是6呢

结构体实现位段 什么是位段

位段的声明和结构是类似的,有两个不同:

1.位段成员必须是int、unsigned int或signed int,在C99中位段成员的类型也可以选择其他类型 2.位段的成员名后边有一个冒号和一个数字

代码演示

struct A { int a : 2; int b : 5; int c : 10; int d : 30; };

这里的A就是一个位段类型 那么位段所占内存的大小又是多少呢?

int main() { printf("%zd\n", sizeof(struct A)); return 0; }

为什么会是8个字节呢? 这就涉及到位段的内存分配了

位段的内存分配

1.位段的成员可以是int、unsigned int、signed int、或者是char等类型 2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的 3.位段涉及很多不确定因素,是不跨平台的,注重可移植的程序应该避免使用位段

画图解释位段的内存分配:两种情况 情况一:不浪费空间

情况二:浪费空间 这说明是第二种情况

但是还有一个问题,申请一个字节后是从前向后存储还是从后向前存储呢? 这里还是画图解释,假设是从后向前存储 这里是VS2022环境测试数据,到了这里大家肯定是知道位段怎么开辟空间了 但是位段的使用还是存在很多问题?比如跨平台问题。

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

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

位段使用的注意事项

位段的几个成员共有同一个字节,这样有些成员的起始位置并不是某个字节的起始位置, 那么这些位置处是没有地址的。内存中每个字节分配一个地址,字节内部的bit位是没有地址的。

所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段成员输入值, 只能是先把值放在一个变量里,然后赋值给位段的成员。

代码举例:

struct A { int a : 2; int b : 5; int c : 10; int d : 30; }; int main() { struct A a = { 0 }; scanf("%d", &(a.b));//这是错误的 //正确的示范 int b = 0; scanf("%d", &b); a.b = b; return 0; }

标签:

计算结构体的大小(结构体内存对齐)、结构体实现位段由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“计算结构体的大小(结构体内存对齐)、结构体实现位段