在我们学习结构体时,可能会碰到几个难以理解的问题,一个是内存对齐,一个是位段。所以我想分享一下我对这两个问题的理解,来帮助大家更好的学习这两个知识点。
内存对齐
struct
{
char i;
char k;
int j;
}s1;
struct
{
char i;
int j;
char k;
}s2;
int main()
{
printf("%d\n", sizeof(s1));
printf("%d\n", sizeof(s2));
return 0;
}
上面的两个结构体,看上去是完全一样的,只有声明变量时的顺序不一样, 那么它们的大小一样吗?
运行后我们发现,它们两个的大小竟然不同,而且如果我们将它们每一个的大小相加起来,得到的也应该是6,不是上面的两个值,那么这是因为什么呢?
这时,就牵扯到了一个叫做内存对齐的东西。
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
. vs的默认值为8,linux默认值为4(32位系统下由于数据总线只有32位,每次只能读取4个字节)
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到在自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
光看文字的话很难看懂,所以下面我会画几幅图来描述一下内存对齐是如何运作的。
struct
{
char i;
char k;
int j;
}s1;
因为第一个i与第二个j是相同类型,所以不存在偏移,k所占据的字节数比j大,所以偏移量为默认对齐数和K的大小的最小值,所以需要偏移到四个字节,所以总共占了八个字节的大小
struct
{
char i;
int j;
char k;
}s2;
看完上面的几个图解,我们了解到如果要合理运用空间,就应该把占用空间较小的成员尽量集中到一起。
那么,问题来了,为什么要有内存对齐呢?
在我们能百度到的大部分资料上,都是这样说的:
- 平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅仅需要一次。(为对齐需要读取多个总线周期)
看文字的话,如果你还不懂,那我再来画一幅图
对于没对齐过的,我们如果想读取这个k,因为我们每次读取四个字节,所以第一次读取的时候只读取到了k的前三个字节,第二次才能读取到k的第四个字节,然后将其进行重组,才能得到这个k。如果是对齐过的,我们就可以一次性读取,虽然使用的空间变多了,但是速度也快了很多。
所以内存对齐的意义就是用空间来换取时间,而空间的价格较为低廉,所以内存对齐的性价比是十分高的。
对齐数的修改
当然,系统默认的对齐数是不能适用于所有情况下的,所以我们可以修改对齐数来适用于我们所处的情景。
我们可以适用一个预处理指令来修改默认的对齐数 # pragma pack(x), 这个x就填入我们想修改的数值
如果我们想要只在一段使用这个对齐数,而在下一段恢复的话,可以这样使用
位段
讲完了内存对齐,下一个就来讲讲这个位段。
什么是位段
位段的声明和结构体是类似的,仅仅存在两个地方的不同:
- 位段的成员必须是int , unsigned int ,signed int, char
- 位段的成员名后边有一个冒号和一个数字
例如:
struct
{
int a : 1;
int b : 3;
int c : 5;
int d : 31;
}s3;
那么,它的大小是多少呢?
为什么会是8呢?
因为一个整形是4个字节,而4个字节有32个比特位,前三个我们分别给的是1,3,5,加起来总共是9,没有达到32个比特位,而第四个占了31个,前三个已经无法在存放这个31,所以这个31单独存放在下一段空间中。总共占了两个整形的空间,所以是2* 4,八个字节。
位段的内存分配
- 位段的成员可以是int,unsigned int ,signed int, char类型
- 位段的空间上是按照需要以四个字节(int)或者一个字节(char)的方式来开辟的。
- 位段设计很多不确定因素,位段是不跨平台的,所以可移植的程序应该避免使用位段
对于这个位段,空间是如何开辟的呢?
因为一个字符型占据一个字节,而一个字节有八个比特位,所以我们需要先知道它们的二进制值
所有的二进制值我都写出来了,同时用框框框起来的是因为二进制数的位数大于我们的位段数,所以我们需要进行截断
这就是位段的存储方式。
位段的跨平台问题
上面的最后一点提到过对于跨平台的程序应该避免使用位段,这是为什么呢?
- in位段被当成有符号的还是无符号数是不确定的。
- 位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义(这个就是我在上个月讲的那个大小端的问题)
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
跟结构相比,位段可以达到相同的效果,虽然可以很好的节省空间,但是存在着跨平台的问题。