在上一篇中我们已经掌握了结构体的基本使用。
现在我们深入讨论一个问题:计算结构体的大小。
我们来看下面一串代码,想一想它们的结果分别是什么?
1、练习
// 练习 1struct S1{char c1 ;int i ;char c2 ;};printf ( "%d\n" , sizeof ( struct S1 ));// 练习 2struct S2{char c1 ;char c2 ;int i ;};printf ( "%d\n" , sizeof ( struct S2 ));// 练习 3struct S3{double d ;char c ;int i ;};printf ( "%d\n" , sizeof ( struct S3 ));// 练习 4- 结构体嵌套问题struct S4{char c1 ;struct S3 s3 ;double d ;};printf ( "%d\n" , sizeof ( struct S4 ));答案:练习1:12练习2:8练习3:16练习4:32
结构体的大小为什么不是每个结构体成员大小相加?
比如S1为什么不是1+4+1=6,而是12
2、结构体对齐规则
结构体的大小如何计算?
首先得掌握结构体的对齐规则
- 1、第一个成员在与结构体变量偏移量为0的地址处
- 2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8 - 3、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 4、如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
根据结构体的对齐规则回顾练习
练习1中
char c1;//c1在偏移量为0的位置
int i ; // 根据规则2,int的大小为4,小于vs默认的4,所以偏移从4的整数倍开始,即i从4开始char c2 ;// 现在偏移量为 1 (c1) +3 (因对其浪费的3个字节) +4 (int) =8, 根据规则2,c2的偏移量为9因为结构体是最大对齐数的整数倍(在这里是int 4),所以c2后面又浪费3个字节,来确定对齐结构体总大小是1 + 3 + 4 + 1 + 3 = 12
同理,
练习2:
结构体总大小是1 + 1 + 2 + 4 = 8(最大对齐数为4)
练习3:
结构体总大小是8 + 1 + 3 + 4 = 16(最大对齐数为8)
练习4:
结构体总大小是1 + 7 + 16(8+1+3+4) + 8 = 32(最大对齐数为8)
//s1和s2存放的内容相同,但是占的字节数不同
3、为什么存在内存对齐?
大部分的参考资料都是如是说的:
1.
平台原因
(
移植原因
)
:
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2.
性能原因
:
数据结构
(
尤其是栈
)
应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。
练习1中,
- 32位的编译器一次内存访问读取4个字节,
- 访问c1后,如果没有内存对齐,会从偏移量为0(没有偏移量,只是方便理解)的位置继续读取i,读取4个字节,
- 但i的字节只读取了3个,还要再进行一次内存访问,访问4个字节,但是读取到i的字节只有一个
如果有内存对齐,则会在访问i时,直接从第四个字节开始访问,只需要访问一次
总体来说:
结构体的内存对齐是拿 空间 来换取 时间 的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
例如前面的练习中S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。
4、修改默认对齐数
//#pragma 预处理指令,可以改变默认对齐数
#include <stdio.h>#pragma pack(8) // 设置默认对齐数为 8struct S1{char c1 ;int i ;char c2 ;};#pragma pack() // 取消设置的默认对齐数,还原为默认#pragma pack(1) // 设置默认对齐数为 1struct 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的次方)