内存对齐和位域

来源:互联网 发布:算量软件有哪些 编辑:程序博客网 时间:2024/06/05 19:46
结构体的内存结构
当我们解决实际问题时,我们会发现编译器提供给我们的内置类型其实不够用,没有办法用同一种类型存储多种类型的数据,C语言中有自己的自定义类型,比如结构、位域、联合、枚举、typedef关键字等。下面我就简单的介绍一下结构和位域。
结构:在一个名字下的一组变量,有时也称为聚集数据类型。
位域:这是结构的一种变形,允许对字中的位进行访问。
当我们申请一个结构体时,它会占多少个字节呢?我们来看一段代码:
#include<iostream>
using namespace std;
int main()
{
struct A {
int a;
int b;
int c;
};
struct B {
char a;
char b;
char c;
};
struct C {
int a;
char c;
int b;
};
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
cout << sizeof(C) << endl;
return 0;
}
输出的结果为:
12
3
12
如图:

补2就是为了方便最后一个int类型便于访问,可以只访问一次,Struct C的对齐模数为4。



前两个结构体的答案大家可以理解,但为什么第三个结构的答案会是这样呢?原来是因为内存对齐,那么为什么编译器会有内存对齐呢?
1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出异常。
2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。因为为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存仅需要一次访问。
许多计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个值k(通常为4或8)的倍数,这就是所谓的内存对齐,这个k就是对齐模数。
内存对齐的规定:
1.数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员自身大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐数。
2.结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)
为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。
3.结构体成员相对首地址偏移量必须是成员大小的整数倍,也就是内存对齐。
4.结构体总大小必须是对齐模数的整数倍,举个例子,你最后加出来的值为30,但是你得对齐模数是8,这时候你就要补充两个字节,让结构体大小为32
5.结构体的对齐模数其实还是自定义的,具体格式为
#pragma back(需要的对其模数)
接下来再看一个例子
#include<iostream>
using namespace std;
int main()
{
struct A {
double d;
int a;
int b;
int c;
};
struct B {
char a;
int b;
char d;
A e;
};
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
return 0;
}
结果为:
24
40
我们再来画一下它的存储情况


位域(一个字节有八位)
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。一、位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:struct 位域结构名
{ 位域列表 };
其中位域列表的形式为: 类型说明符 位域名:位域长度
位域的存储要求:
1.一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
struct bs
{
unsigned char a : 4;
unsigned char b : 5;/*从下一单元开始存放*/
unsigned char c : 2;
};
cout << sizeof(bs);
结果为2
2.由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。
3.位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct k
{
char a : 1;
char : 2;/*该2位不能使用*/
char b : 3;
char c : 3;
};
结果为2
位域的使用:
位域的使用和结构成员的使用相同,其一般形式为:
“位域变量名·位域名”位域允许用各种格式输出。
struct k
{
unsigned a : 3;
unsigned b : 4;
unsigned c : 5;
}kk, *ko;
kk.a = 2; kk.b = 6; kk.c = 5;
cout << kk.a << " " << kk.b << " " << kk.c << endl;
ko = &kk;
ko->a=1;
ko->b&=3;//ko->b&=3和ko->b=ko->b&3等价(6和3使用二进制作与(&)运算)
ko->c|=2;//同理5和2使用二进制作或(|)运算,“、=”、“&=”是复合位运算
cout << ko->a << " " << ko->b << " " << ko->c << endl;
return 0;
位域的大小;
如果位域字段之间穿插着非位域字段,则不进行压缩;
比如:
struct k
{
int a : 2;
double b;
char c : 6;
};
cout << sizeof(k) << endl;
结果为24
调换一下位置
struct k
{
int a : 2;
char c : 6;
double b;
};
结果为16
一定要自己亲自动手写代码才能正真理解哦。
原创粉丝点击