首先来看一个例子:
#include
#include
struct s
{
char a;
int b;
char c;
};
int main()
{
printf("sizeof(s)= %d\n",sizeof(struct s));
system("pause");
return 0;
}
如果不存在内存对齐这个问题,这个结构体应该占1+4+1=6个字节;
然而事实上它占了12个字节。
1.什么是内存对齐
结构体内存对齐:
元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。
从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始。
2.为什么存在内存对齐
举例1:
#include
#include
struct s
{
int a;
char b;
double c;
char d;
};
int main()
{
printf("sizeof(s)= %d\n",sizeof(struct s));
system("pause");
return 0;
}
分析:
int占4个字节
char占1个字节
float占4个字节
double占8个字节
a在偏移量为0的地址处,即它对齐到0号地址处的位置,占四个字节,即占用0,1,2,3;
第二个元素的大小为char型,占1个字节,要求对齐到自己对齐数的整数倍处,4是1的倍数,故而,b占用4偏移;
接下来可用偏移为5偏移,接下来存double c;
由于5不是8的倍数,所以向后偏移5,6,7,都不是8的倍数,偏移到8时,8是8的倍数,故而c从8处开始存储,占用8,9,10,11,12,13,14,15偏移;
现在可用偏移为16偏移,最后该存char d ;
因为16是1的倍数,故d占用16偏移,
接下来在整体向后偏移一位,现处于17偏移,min(默认对齐参数,类型最大字节数)=8;因为17不是8的倍数,所以继续向后偏移18…23都不是8的倍数,到24偏移处时,24为8的整数倍,
故而,该结构体大小为24个字节。
如图:
举例2:
#include
#include
struct S1
{
char c1;
char c2;
int i;
};
struct S2
{
char c1;
struct S1 s3;
double d;
};
int main()
{
printf("sizeof(S1)= %d\n",sizeof(struct S1));
printf("sizeof(S2)= %d\n", sizeof(struct S2));
system("pause");
return 0;
}
结构体S1的大小为1(char)+1(char)+2(偏移量)+4(int)=8,S1的对齐数就是其结构体成员中最大的对齐数,即4;
在结构体S2中,对齐数分别是0、4、8;
首先 c1 对齐在0号地址的位置,其占用1个字节,
根据规则,嵌套的结构体S1要对齐到自身对齐数4的整数倍处,所以S1对其在4号地址处,其大小是8个字节;
d 的对齐数为8,所以它要对齐到16号地址处,
所以此时计算出S2的大小是1(char)+3(偏移量)+8(struct S1)+4(偏移量)+8(double)=24,
结构体S2中个成员的对齐数分别为0、4、8,因为24是8的倍数,符合规则。
所以S2的总大小就是24个字节。
第一个成员在与结构体变量偏移量为0的地址处。
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数=编译器默认的一个对齐数与该成员大小的较小值,在VS环境下默认值为8,在Linux环境下默认值为4。
结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
如果嵌套了结构体的情况,被嵌套的结构体对齐到其自身对齐数的整数倍处(结构体的对齐数就是其内部成员中最大的对齐数),此时结构体的整体大小就是所有最大对齐数(含被嵌套结构体的对齐数)的整数倍。
举例三:
#include
#include
struct S1
{
char a;
int i;
char b;
};
struct S2
{
int a;
char b;
short c;
double d;
int f;
struct S1 s;
};
struct S3
{
char c1;
struct S2 s;
double d;
};
int main()
{
printf("sizeof(S1)= %d\n",sizeof(struct S1));
printf("sizeof(S2)= %d\n", sizeof(struct S2));
printf("sizeof(S3)= %d\n", sizeof(struct S3));
system("pause");
return 0;
}
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
1.位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:
struct 位域结构名
{ 位域列表 };
其中位域列表的形式为:
类型说明符 位域名:位域长度
例如:
struct bs
{
int a:8;
int b:2;
int c:6;
};
该结构体共占了两个字节。
因为:都为数据类型都为int型,位域a占8位,位域b占2位,位域c占6位,一个字节为8位。
2.空域:宽度为 0 的一个未命名位域强制下一位域对齐到其下一成员的数据类型边界。
struct bs
{
char a:1;
char :0; //空域
char b:2; //从下一个单位开始存放
char c:3;
};
该结构体共占了2个字节。
注意:
struct k
{
int a : 1;
int : 2; /*该2位不能使用*/
int b : 3;
int c : 2;
};
该结构体共占了4个字节。
从以上分析可以看出,位域在本质上就是一种结构类型, 不过其成员是按二进位分配的。
位域的使用和结构成员的使用相同。
其一般形式为: 位域变量名.位域名
#include
#include
struct bs
{
unsigned a : 1;
unsigned b : 3;
unsigned c : 4;
} bit, *pbit;
int main()
{
bit.a = 1;
bit.b = 7; //注意:位域的赋值不能超过该域所能表示的最大值,如b只有3位,能表示的最大数为7,若赋为8,就会出错
bit.c = 15;
printf("%d,%d,%d\n", bit.a, bit.b, bit.c);
pbit = &bit;
pbit->a = 0;
pbit->b &= 3; //复合的位运算符"&="(详解如下)
//相当于: pbit->b=pbit->b&3位域b中原有值为7,与3作按位与运算的结果为3(111&011=011,十进制值为 3)
pbit->c = 1;
printf("%d,%d,%d\n", pbit->a, pbit->b, pbit->c);
system("pause");
return 0;
}
按位与(&)
参加运算的两个数,换算为二进制(0、1)后,进行与运算。只有当相应位上的数都是1时,该位才取1,否则该为为0。
将10与-10进行按位与(&)运算:
0000 0000 0000 1010
1111 1111 1111 0110
-----------------------
0000 0000 0000 0010
所以:10 & -10 = 0000 0000 0000 0010
1.看下面的结构体:
#include
#include
struct foo1
{
char a : 2;
char b : 3;
char c : 1;
};
struct foo2
{
char a : 2;
char b : 3;
char c : 7;
};
int main()
{
printf("第一个结构体大小:%d\n",sizeof(struct foo1));
printf("第二个结构体大小:%d\n", sizeof(struct foo2));
system("pause");
return 0;
}
结果为:
分析:
如果按照正常的内存对齐规则, 这两个结构体大小均应该为3,那么问题出在哪了呢?
首先通过这种现象我们可以肯定的是:
带有’位域’的结构体并不是按照每个域对齐的,而是将一些位域 成员’捆绑’在一起做对齐的。
以foo1为例,这个结构体中成员都是char型,而且三个位域占用的总空间为:6 bit < 8 bit(1 byte),
这时编译器会将这三个成员’捆绑’在一起做对齐,并且以最小空间作代价,故sizeof(struct foo1) = 1。
foo2这个结构体成员类型也都是char型,但三个成员位域所占空间之和为:9 bit > 8 bit(1 byte),
这里位域是不能跨越两个成员基本类型空间的,这时编译器将a和b两个成员’捆绑’按照char做对齐,而c单独拿出来以char类型做对齐, 这样实际上在b和c之间出现了空隙,但这也是最节省空间的方法了。
再看一种结构体定义:
#include
#include
struct foo3
{
char a : 2;
char b : 3;
int c : 1;
};
int main()
{
printf("结构体大小:%d\n",sizeof(struct foo3));
system("pause");
return 0;
}
分析:
在foo3中虽然三个位域所占用空间之和为6 bit < 8 bit(1 byte),
但是由于char和int的对齐系数是不同的,是不能捆绑在一起,
那是不是a、b捆绑在一起按照char对齐,c单独按照int对齐呢?
不可以。
sizeof(struct foo3)=8。编译器把a、b、c一起捆绑起来并以int做对齐了。
不够一个类型的size时,将按其中最大的那个类型对齐。此 处按int对齐。
例题:
#include
#include
struct s1
{
int i : 8;
int j : 4;
int a : 3;
double b;
};
struct s2
{
int i : 8;
int j : 4;
double b;
int a : 3;
};
int main()
{
printf("第一个结构体大小:%d\n",sizeof(struct s1));
printf("第二个结构体大小:%d\n", sizeof(struct s2));
system("pause");
return 0;
}
分析:
第一个结构体中,
i,j,a共占15个位,不足8个字节,按8字节对齐,共16字节
第二个结构体中,i,j共占12位,不足8字节,按8字节对齐,a也按8字节对齐,加上double共8+8+8=24个字节
如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止 ;
如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++,GCC采取压缩方式;
如果位域字段之间穿插着非位域字段,则不进行压缩;
整个结构体的总大小为最宽基本类型成员大小的整数倍。
哈哈,可以说是很清晰了哦。