解决 sizeof 求结构体大小的问题
一、C/C++ 中不同数据类型所占用的内存大小
数据类型 | 32位 | 64位 |
---|---|---|
char | 1 | 1 |
int | 4 | 大多数4(少数8) |
short | 2 | 2 |
long | 4 | 8 |
float | 4 | 4 |
double | 8 | 8 |
*指针 | 4 | 8 |
(单位都为字节)
结构体(struct):比较复杂,对齐问题需要考虑清楚。
联合(union):取所有成员中最长的作为联合的长度。
枚举(enum):根据数据类型。
二、sizeof 计算单层结构体大小
运算符sizeof可以计算出给定类型的大小,对于 32 位系统来说,
sizeof(char) = 1; sizeof(int) = 4。
基本数据类型的大小很好计算,我们来看一下如何计算构造数据类型的大小。
C语言中的构造数据类型有三种
数组
结构体
共用体
数组是相同类型的元素的集合,只要会计算单个元素的大小,整个数组所占空间等于基础元素大小乘上元素的个数。
结构体中的成员可以是不同的数据类型,成员按照定义时的顺序依次存储在连续的内存空间。和数组不一样的是,结构体的大小不是所有成员大小简单的相加,需要考虑到系统在存储结构体变量时的地址对齐问题。看下面这样的一个结构体:
1
2
3
4
5
6struct stu1
{
int i;
char c;
int j;
}用sizeof求该结构体的大小,发现值为 12 。int占 4 个字节,char占 1 个字节,结果应该是 9 个字节才对啊,为什么呢?
先介绍一个相关的概念——偏移量。偏移量指的是结构体变量中成员的地址和结构体变量地址的差。结构体大小**等于最后一个成员的偏移量加上最后一个成员的大小**。显然,结构体变量中第一个成员的地址就是结构体变量的首地址。因此,第一个成员i的偏移量为 0。第二个成员 c 的偏移量是第一个成员的偏移量加上第一个成员的大小(0+4),其值为 4;第三个成员j的偏移量是第二个成员的偏移量加上第二个成员的大小(4+1),其值为 5。
然而,在实际中,存储变量时地址要求对齐,编译器在编译程序时会遵循两条原则:
- 结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
- 结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。
上面的例子中前两个成员的偏移量都满足要求,但第三个成员的偏移量为5,并不是自身(int)大小的整数倍。编译器在处理时会在第二个成员后面补上 3 个空字节,使得第三个成员的偏移量变成8。结构体大小等于最后一个成员的偏移量加上其大小,上面的例子中计算出来的大小为 12,满足要求。
再来看另外一个例子:
1
2
3
4
5struct stu2
{
int k;
short t;
}成员k的偏移量为 0;成员t的偏移量为 4,都不需要调整。但计算出来的大小为 6,显然不是成员 k 大小的整数倍。因此,编译器会在成员 t 后面补上 2 个字节,使得结构体的大小变成8从而满足第二个要求。
由此可见,结构体类型需要考虑到字节对齐的情况.
不同的顺序也会影响结构体的大小
对比下面两种定义顺序:
1
2
3
4
5
6
7
8
9
10
11
12struct stu3
{
char c1;
int i;
char c2;
}
struct stu4
{
char c1;
char c2;
int i;
}虽然结构体 stu3 和 stu4 中成员都一样,但 sizeof(struct stu3) 的值为 12 而 sizeof(struct stu4) 的值为 8
共用体中的成员公用同一段内存,所以整个联合体的 sizeof 是所有成员中占用内存最大的成员的 sizeof,联合体要考虑内存对齐,整体空间长度要是公用体中长度最大的数据类型的整数倍
1
2
3
4union st{
char a[9];
int b[2];
}s结果分析:
sizeof(a)=9*1=9
sizeof(b)=2*4=8
选两者中最大的一个,即 9,考虑内存对齐,整体空间长度要是公用体中长度最大的数据类型的整数倍,在这里是 int 占用空间的整数倍,比 9 大的且是 4 的整数倍的最小数是 12
拓展枚举类 enum 型空间计算
enum 只是定义了一个常量集合,里面没有“元素”,而枚举类型是当做 int 来存储的,所以枚举类型的 sizeof 值都为 4
1 | enum color(red,pink,white,black)c; |
三、sizeof计算嵌套的结构体大小
对于嵌套的结构体,需要将其展开。对结构体求 sizeof 时,上述两种原则变为:
展开后的结构体的第一个成员的偏移量应当是被展开的结构体中最大的成员的整数倍。
结构体大小必须是所有成员大小的整数倍,这里所有成员计算的是展开后的成员,而不是将嵌套的结构体当做一个整体。
被展开的结构体的规则同单结构体 sizeof 计算规则。
原则 1:
1 | struct stu5 |
结构体 stu5 的成员 ss.c
的偏移量应该是 4,而不是 2。整个结构体大小应该是 16,即 4+(4+4)+4,第二个 4是 char c
的扩展(由后面 int j
导致)。
原则 2:
1 | struct stu5 |
结构体 ss
单独计算占用空间为8,而 stu5
的 sizeof 则是 20 (由17扩展至 20,而不是扩展到 24),不是 8 的整数倍,这说明在计算 sizeof(stu5)
时,将嵌套的结构体 ss
展开了,这样 stu5 中最大的成员为 ss.j
,占用 4 个字节, 20 为 4 的整数倍。如果将 ss
当做一个整体,结果应该是 24 了。
另一个常见的例子:
结构体中包含数组,其 sizeof 应当和处理嵌套结构体一样,将其展开
1 | struct ss |
补充另外一个涉及数据类型以及内存存储的问题
内存中的数据并非保存在任意地址。处理器通常按照和其字大小相同的块读取内存数据;那么考虑到效率因素,编译器会按照块大小的整数倍对内存中的实体进行地址对齐。因此在 32 位的处理器上,一个 4 字节整型数据肯定存放在内存地址能被 4 整除的地方。
下面,假设系统中整型数据和指针大小均为 4 字节。
现在有一个指向整型的指针。如上所述,整型数据可以存放在内存地址 0×1000 或者 0×1004 或者 0×1008,但是决不会存放在 0×1001 或者 0×1002 或者 0×1003 或者其他不能被 4 整除的任何地址。所有是4整数倍的二进制数都是以 00 结尾。实际上,这意味着对于所有指向整型的指针,它的最后两位总是 0。