c结构体大小的计算
首先来看一个例子:
#include<stdio.h>
#include<stdlib.h>
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.为什么存在内存对齐
- 平台原因(移植问题):
一些资料上是这样说的,
“不是所有的硬件平台都能访问任意地址上的任意数据;
某些硬件平台只能在某些特定地址处取某些特定的数据,否则就会抛出硬件异常”。
也就是说在计算机在内存读取数据时,只能在规定的地址处读数据,而不是内存中任意地址都是可以读取的。 - 性能问题:
正是由于只能在特定的地址处读取数据,所以在访问一些数据时,对于访问未对齐的内存,处理器需要进行两次访问;
而对于对齐的内存,只需要访问一次就可以。
3.偏移量
二。 结构体的大小
举例1:
#include<stdio.h>
#include<stdlib.h>
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<stdio.h>
#include<stdlib.h>
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<stdio.h>
#include<stdlib.h>
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个字节。
注意:
- 如果一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域;
- 位域的长度不能大于指定类型固有长度,比如说int的位域长度不能超过32;
- 位域可以是无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。
struct k
{
int a : 1;
int : 2; /*该2位不能使用*/
int b : 3;
int c : 2;
};
该结构体共占了4个字节。
从以上分析可以看出,位域在本质上就是一种结构类型, 不过其成员是按二进位分配的。
二、位域的使用。
位域的使用和结构成员的使用相同。
其一般形式为: 位域变量名.位域名
#include<stdio.h>
#include<stdlib.h>
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<stdio.h>
#include<stdlib.h>
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<stdio.h>
#include<stdlib.h>
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<stdio.h>
#include<stdlib.h>
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采取压缩方式;
-
如果位域字段之间穿插着非位域字段,则不进行压缩;
-
整个结构体的总大小为最宽基本类型成员大小的整数倍。
哈哈,可以说是很清晰了哦。