前面我们简单的了解了一下结构体,但是结构体的内存是如何分配的呢?
struct
{
int x;
char y;
}a;
int main()
{
printf("%d\n",sizeof(a));//输出8
return 0;
}
我们知道int类型变量占4个byte,一个char类型变量占1个byte,
那么结构体应该占4 + 1 = 5byte,但是实际上却输出
8byte,这就是内存对齐所导致的!!!
内存对齐
1、什么是内存对齐?
众所周知现代计算机内存都是按照byte字节去划分的,理论上讲任何类型变量的访问都可以从任何地址开始,但是理论总是和实际差一点意思,计算机系统对基本类型数据在内存中存放的位置是有限制的,他们要求这些数据首地址是某个数X(通常为4或8)的倍数,这就是内存对齐,
2、为什么要内存对齐?
虽然内存以字节为单位,但是大部分处理器并不是以字节块来取内存的。一般都是2字节、4字节、8字节、16字节、32字节来去内存,我们一般把上述单位称为内存存取粒度;我们以4字节存取粒度为例子,一个int变量从内存地址为1开始联系的四个字节地址,如果处理器要想取到这个数据,怎么办?,
第一次从地址0开始取四个字节,剔除不需要的数据,第二次从地址4开始取四个字节,剔除不需要的数据,最后将两块数据合并放入寄存器使用。
但是我们使用内存对齐,int类型数据只能存放在按照对齐规则的内存中,比如0地址开始的内存,那么处理器一次性直接就可以拿到想要的数据,无需额外的操作,这不就提高了效率吗!!!
3、怎么样内内存对齐?
先来介绍接个概念:
一、每一个特定的平台编译器都有字节默认的对齐系数,linux下为#pragma pack(4),windows下为为#pragma pack(8);我们也可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变。
二、有效对齐值:是给定的 #pragma pack(n) 和结构体中最长数据类型长度中较小的那一个。有时有效对其值也叫对齐单位;
内存对齐规则:
1、结构体第一个成员的偏移量为0,以后每一个成员相对于结构体首地址的偏移量offset都是该成员大小与有效对齐值中较小的那一个的整数倍,如果有需要,编译器会在变量成员之间加上填充字节。
2、结构体的总大小为有效对齐值的整数倍,如果有需要编译器会在最后一个成员之后加上填充字节。
//linux环境下 对齐系数为 : 4
//下面结构最长数据类型4字节,所以有效对齐值 = 4
struct
{
int i;
char c1;
char c2;
}x1;
struct
{
char c1;
int i;
char c2;
}x2;
struct
{
char c1;
char c2;
int i;
}x3;
int main()
{
printf("%d\n",sizeof(x1));//输出8
printf("%d\n",sizeof(x2));//输出12
printf("%d\n",sizeof(x3));//输出8
return 0;
}
4、修改对齐系数
不同的平台pragma pack默认值不同。但是我们可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变。
注意:修改完后一定要还原默认值
#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为8
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S1));//输出12
printf("%d\n", sizeof(struct S2));//输出6
return 0;
}
这里皆可以了解了,我们以后在定义结构体时尽量将小类型往前放,大类型向后放,这样可以减少空间浪费;
位段
位段使用结构体来实现的,位段的声明和结构体差不多,但是每一个成员后面是一个或多个位的字段,这些不同长度的字段实际存储于一个或多个整型变量中。位段的成员必须声明为int、signed int 或unsigned int类型,其次成员名后面跟一个冒号和一个整数;
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
printf("%d\n", sizeof(struct A));//输出8
位段内存如何分配?
1、位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型。
2、 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3、位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
位段跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问
题。 - 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的
位还是利用,这是不确定的
总结:跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。