1.结构体大小的计算
(1)结构体内存对齐的规则
1. 第一个成员在与结构体变量偏移量为 0 的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的对齐数 与 该成员大小的 两者之间的较小值 。
VS 中默认的值为 8 gcc没有默认对齐数,那么对齐数就是该成员的大小
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
例如
struct S1 { char c1;//大小为1,默认对齐数为8,取其小就是1,所以其对齐数是1 int a;//大小为4,默认对齐数为8,取其小为4,所以其对齐数就是4 char c2; } int main() { struct S1 s1={0}; printf("%d\n",sizeof(s1)); }
其内存分配如图所示
其中的成员占了9个内存单元,但是结构体的内存大小是其最大对齐数的整数倍,这里的最大对齐数是4,所以在9以上的,最小的,4的倍数是12
再来一例,与上面同理,直接看图
struct S2 { char c1; char c2; int a; } int main() { struct S2 s2={0}; printf("%d\n",sizeof(s2)); }
成员的内存总共为8个内存单元,刚好也是4的倍数
可以看到两个结构体的成员是相同的,但是后者占用内存比前者少,所以总结起来
将占用内存小的成员写在前面 ,这样能更加高效的利用空间
(2) 那么既然有空间的浪费,为什么不紧密的存放每一个成员呢,内存对齐的意义
1. 平台原因 ( 移植原因 ) :
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因 :
数据结构 ( 尤其是栈 ) 应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说: 结构体的内存对齐是拿空间 来换取 时间 的做法。
(3)修改默认对齐数
结构在对齐方式不合适的时候,可以自己更改默认对齐数。
#pragma 这个预处理指令,可以改变默认对齐数,例如
#pragma pack(1)设置默认对齐数为1,这样每一个结构体成员都是紧密排列的了
#pragma pack(1)//设置默认对齐数为1 struct S2 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认 补充(位段) 位段的内存分配 1.位段的成员可以是intunsignedint signedint或者是char(属于整形家族)类型 2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的 3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段 struct S { int a; int b; int c; int d; }; 按照上面的结构体的计算规则,那么这个结构体的大小为16 对于位段是8个字节 struct S { int a:2;//冒号后的数字表示a只需要2个比特位 int b:5; int c:10; int d:30; } //总共需要47个比特位,那么6个字节足够(6*8)=48,计算的结果却为8个字节 注意位段是按整型大小的字节进行开辟的
注意:
struct S { int a:2; int b:5; int c:10; int d:33;//不能超过32个比特,报错:d的位域大小无效 }
本来int类型需要占用32位空间,现在只需要占用2个比特位的空间,位段的使用节省了空间大小
举例
struct S { char a:3; char b:4; char c:5; char d:4; } int main() { struct S s={0}; s.a=10;//二进制序列1010 s.b=20; s.c=3; s.d=4; return 0; }
首先struct S s={0};所以先给s分配8个比特位
如上图所示a只有3个比特位,但是s.a=10//二进制序列为1010,所以内存中只能存3个比特位,即1010中的010
s.b=20//二进制序列10100占5个比特,但是b只有4个比特,所以只能传“0100”
s.c=3//二进制序列011占3个比特,但是c有5个比特,前面补0,即“00011”
s.d=4//二进制序列为100,但是d占4个比特,前面补0,即“0100”
所以总体写下来如图所示
阅读内存时,内存是16进制数字,则四个2进制位为一个16进制,从左往右依次读
0010 0010 0000 0011 0000 0100
2 2 0 3 0 4
位段的跨平台问题
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。
( 16 位机器最大 16 , 32 位机器最大 32 ,写成 27 ,在 16 位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
总结
跟结构相比,位段可以达到同样的效果,可以很好的节省空间,但是有跨平台的问题存在
应用:用位段封装网络上传输的数据包等
2.枚举的大小(4个字节)
enum Color//颜色 { RED=1, GREEN=2, BLUE=4 }; enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。 clr = 5; enum Sex { MALE, FEMALE, SECRET } int main() { enum Sex s=MALE; printf("%d\n",sizeof(s)); return 0; } 结果为4
3.联合大小的计算
(1)计算规则
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
union Un { int a;//成员大小为4,默认对齐数为8,对齐数为4 char arr[5]; //成员大小char是1,默认对齐数是8,得到对齐数是1,不要拿数组的大小进行计算 } int main() { union Un u; printf("%d\n",sizeof(u)); return 0; }
如果联合体如下图所示
那么其占用内存总大小为5个内存单元,但是联合的大小
至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
5(最大成员大小)不是(最大对齐数)4的整数倍,所以应该按下图进行内存分配
如图浪费了3个内存单元,使得总内存为8个字节,为最大字节数(4)的整数倍