内存对齐
内存对齐的引例
例一 struct A{
char a;
char b;
char c;
};
易知,结构体A的大小为3。
例二 struct B{
Int a;
int b;
int c;
};
易知,结构体B的大小为12。
例三 struct C{
int a;
char b;
int c;
};
经过编译器编译,得结构体C的大小为12。
(1) 对于结构体A和结构体B,它们的结构体内各有三个相同类型的数据。计算它们结构体的大小无需费心,只需计算三个相同类型的
数据所占的空间大小即可。
(2) 对于结构体C来说,这个结构体包含了两种不同类型的数据。如果我们用计算结构体A和结构体B大小的方法来计算结构体C的大小,
得出的结果是 9,显然这个结果并不正确。为什么此时的结构体大小比实际数据所占的字节数要大呢?通过例三结构体大小的计算,
我们引出内存对齐的内容。
一. 内存对齐的概念
内存对齐属于编译器的管辖范围,编译器通过内存对齐,使数据存放在适当的位置,便于读取。
二. 内存对齐的原因
(1)硬件原因: 不是所有的硬件平台都能访问任意地址上的任意数据的;有些硬件平台只能在某些地址处取某些特定类型的数据,否则
抛出硬件异常。
(2)性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;
而对齐的内存访问仅需要一次访问。
为了加强程序的可移植性,减少程序的运行时间,编译器对数据进行内存对齐处理。虽然内存对齐在一定程度上浪费了内存空间,但相对
的缩短了程序的运行时间。从长远角度来看,这种牺牲空间换取时间的做法还是明智的。
三. 内存对齐的使用规定
每个特定平台上的编译器都有自己默认的"对齐系数",也称"对齐模数"。
(1) 首地址的选择:结构体变量的首地址是其最长基本类型成员的整数倍
编译器再给结构体开辟空间时,先找到结构体中最宽的基本数据类型,然后找寻内存地址是该基本数据类型的宽度的整数倍的位置,
作为结构体的首地址。将这个最宽的基本数据类型的大小称为最大对齐模数。
(2) 数据成员的对齐规则:第一个数据放在结构体的首地址处,要求以后每个数据存储的地址相对于结构体首地址是自身大小的整数倍。
如有需要,编译器会在成员之间加上填充字节。
为结构体的数据成员开辟空间之前,编译器首先检查开辟空间的首地址相对于结构体空间的首地址的偏移量(offset)是否为数据自身
大小的整数倍,若是,则按序存放;若不是,编译器会在数据之间加上一定的填充字节,以达到整数倍的要求。
(3) 若涉及结构体的嵌套:如果一个结构体内有其他结构体的成员,则结构体成员要从它自身的最大对齐模数的整数倍的地址处开始存储。
如有需要,编译器会在上一个结构体的结尾处加上填充字节。
(4) 结构体大小的计算:结构体的总大小一定是结构体内最大对齐模数的整数倍。如有需要,编译器会在结构体的最后加上填充字节。
(5) 可以通过预编译命令 #pragma pack(常量) 来改变编译器自定的对齐模数,自主设定对齐模数。
四. 结构体大小的计算步骤
下面是一个简单的例子,我们来计算它的结构体大小
struct A{
double d;
int a;
int b;
int c;
};
struct AA{
char a;
int b;
char c;
A d;
};
cout<<sizeof(AA);
(1) 找出结构体A中的最大对齐模数:double(8);
(2) 计算结构体A中各数据所占总空间的大小,需要考虑各数据是否从自身大小的整数倍地址处开始存储,规则(2);
(3) 计算结构体A的总大小,需要考虑总大小是否为最大对齐模数8的整数倍,若不是,加上填充字节。规则(4);
(4) 找出结构体AA中的最大对齐模数,即为结构体A的最大对齐模数 double(8);
(5) 计算结构体AA各数据所占总空间的大小,规则(2);
(6) 考虑到结构体A在结构体AA中的存储,判断结构体A的首元素的地址是否为最大对齐模数的整数倍,若不是,加上填充字节。
规则(3);
(7) 计算结构体AA的总大小,判断是否为最大对齐模数的整数倍,若不是,加上填充字节。规则(4)。
经编译器编译,结构体A的大小为24,结构体AA的大小为40。
位域
一. 位域的概念
为了节省存储空间,并使处理简便,C语言提供了一种数据结构类型,称为“位域”或“位段”。
所谓位域是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。
这样就可以把几个不同的对象用一个字节的二进制位域来表示。
二. 位域的定义
(1)位域的定义和结构体的定义形式相似:
struct 位域结构名
{ 位域列表 };
位域列表的形式为:
数据类型 位域名:位域长度;
例 struct A{
int a:4;
int b:2;
int c:1;};
(2)位域变量的说明与结构体变量的说明形式也相似:
第一种:先定义后说明
第二种:定义的同时说明
(3)位域的使用可通过位域名调用或指针调用。
三. 位域的存储要求
(1) 一个位域只能存储在一个字节中,不能跨两个字节存储。当一个字节的所剩空间不足够存储另一个位域时,要从下一单元起存放
该位域。也可有意从下一单元开始存储某位域。
(2) 由于位域不能够跨两个字节存储,所以一个位域的大小不能超过一个字节,即八个位。
(3) 位域可以无位域名,只用作填充或调整位置。无名的位域是不能被调用的。
(4) 当位域字段之间穿插着非位域字节,则不再进行压缩。