结构体长度问题

很久之前,我以为结构体的长度就是各个成员的长度之和,对此深信不疑,在有限的经历中,这似乎也没有出现过什么问题。直到有一天,使用sizeof函数求一个结构体的长度时,意外地发现结构体的长度居然大于各成员长度之和。查阅资料才知道,原来编译器为了加快数据存取速度,默认情况下会对结构体本身和其中的成员的储存位置进行调整,以达到字节对齐。所以,结构体的长度是大于等于各成员长度之和的。我恍然大悟,以为找到了真理。秉持着这样的“真理”过了好长一段时间,相安无事。直有一次,我再次用sizeof求一个windows.h头文件下的一个结构体时,发现该结构体的长度刚好等于各成员的长度之和,而按照字节对齐的规则分配结构体内存空间的话,该结构体的长度应该大于各成员长度之和才对。我有迷茫了,到底是怎么回事?心想大概是sizeof函数出了点差错什么的。然而,最致命的一击是,我发现有些结构体,如果用sizeof求其长度,得到的结构体长度居然小于各成员的长度之和!甚是诡异,人生观就这样崩溃了!
遭遇了几次挫折之后,我觉得我没有完全理解结构体,于是花了几天时间,查阅很多关于结构体方面的资料,深刻剖析了结构体的结构。下面是一些关于结构体长度计算的总结。

结构体的长度与结构体占用空间

结构体的长度与结构体占用的空间是两个不同的概念,虽然有时候可以混用,但并不等同。结构体的长度是各成员长度之和,这句话本身并没有什么问题。关键是,系统为结构体分配了多少空间。用sizeof求结构体的长度,这种方法并不可靠,因为sizeof函数是用来求结构体占用空间的大小,而不是长度。一般情况下,结构体的实际占有空间的大小是等于结构体长度的,但也有例外。从实际的情况来看,用sizeof求结构体的占有空间,结果可能是刚好等于各成员大小之和,也可能是大于各成员大小之和,甚至是小于各成员大小之和的。下面,将详细探讨结构体占用空间的问题。

1、结构体占据空间大小与字节对齐

为了加快数据存取的速度,编译器默认情况下会对结构体成员和结构体本身(实际上其他数据对象也是如此)存储位置进行处理,使其存放的起始地址是一定字节数的倍数,而不是顺序存放,称为字节对齐。字节对齐的规则为:
(1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
(2)结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;
(3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

2、结构体占用空间的计算方法

结构体占用的空间跟成员的数据类型、成员的数目、还有成员的排列顺序有关。
(1)成员的数据类型都相同时:
数组由多个相同数据类型的元素组成,一个数组成员可视为多个普通成员。

   结构体占用空间=成员个数×成员数据类型的长度。

(2)成员的数据类型不相同时:
如果结构体中含有结构体成员,则要先按照字节对齐原则对结构体成员的长度进行计算,然后再把结构体成员拆解,其成员的顺序不变。

a.分析各个成员长度;
b.找出最大长度的成员长度L(结构体的长度一定是该成员的整数倍);
c.并按最大成员长度出现的位置将结构体分为若干部分;
d.各个部分长度一次相加,求出大于该和的最小L的整数倍即为该部分占用的空间
e.将各个部分长度相加之和即为结构体总的占用空间。

(3)特殊结构体的占用空间计算

a.对于一个没有任何成员的结构体,它占用的空间为1字节,而不是0。
b.如果一个结构体中含有静态的成员,那么用sizeof函数算出来的结构体占用空间可能会比实际长度小。这是由于静态成员储存在数据段,
而sizeof函数只会统计在栈中的成员。

3、例子

(1)空结构体

struct A{
}

空结构体也占用空间,大小为1,故sizeof(A)=1。
(2)成员数据类型相同

struct B{
    int x;
    int y;
}
struct C{
    int x;
    int a[4];
    int y;
}

成员数据类型相同时,结构体的占用等于各成员长度之和。即sizeof(B)=4+4=8;sizeof(C)=4+4×4+4=24。
(3)成员的数据类型不同
成员的数据类型不同时,结构体的占用空间不但跟数据类型、成员数目有关,还跟成员的排列次序有关。
a.

struct D{
    char c;
    char s;
    int x;
}

x成员的长度最大,因此要以它作为基准进行字节对齐。该结构体在内存中的布局为:

|char|char|---|---|-----int-----|

因此sizeof(D)=1+1+2+4=8。
b.

struct E{
    char c;
    int x;
    char s;
}

结构体E的成员数目和类型都与结构体D相同,不同的是成员的顺序。该结构在内存中的布局如下:

|char|---|---|---|-----int----|char|---|---|---|

因此sizeof(E)=1+3+4+1+3=12。
c.

struct F{
    char c;
    int x;
    double y;
}

结构体F的内存布局如下:

|char|-----int----|---|---|---|-----------double---------|

sizeof(F)=1+4+3+8=15。
d.

struct G{
    char c;
    struct E e;
}

该结构体含有一个结构体成员e,上面已经计算出结构体E的大小为12字节,E中的最长的成员类型是int型。该结构体在内存总的布局为:

|char|--|--|--|------struct E------|
即|char|--|--|--|char|--|--|--|---int---|char|--|--|--|

sizeof(G)=1+3+12=16。
e.

struct H{
    int x;
    stactic int y;
}

该结构体含有静态的成员,静态成员不在栈中,因此sizeof函数是不会计入静态成员的长度的。sizeof(H)=4;

如何让结构体的长度与占用的空间等同?

结构体的长度与其占用的空间并不等同,但有时候不得不通过求结构占用空间来获得结构体的长度。如果求结构体的占用空间,直接用sizeof函数就可以了。而如果求结构体的长度,最好就是直接计算各成员的长度之和,这样绝对不会错。但有时候,这样做会很麻烦,特别是结构体的成员比较多的时候。这时,我更希望用sizeof函数来求结构体的长度。
结构体的长度与占用空间之所以不等,是因为字节对齐惹的祸。编译器默认的字节对齐是按照最宽的成员数据类型为基准的,既然有默认,那么是否有自定义呢?答案是肯定的。如果将结构体字节对齐的字节数设定为1字节,那么结构体的长度与占用空间就一致了。这就是为什么对Windows.h头文件里面的结构体使用sizeof函数时,得出的结果与结构体各成员长度之和刚好相等。

在VC或者VS编译器中,使用预处理命令#pragma pack(n)可以设定对齐字节数n(n=1,2,4,8,16)。例如:
//结构体A采用默认的字节对齐
struct A{
    char c;
    int  x;
};

#pragma pack(push)//保存字节对齐数
#pragma pack(1)//设定对齐字节数为1
struct B{
    char c;//1字节
    int  x;//4字节
};
#pragma pack(pop)//恢复对齐字节数

结构体A与结构体B的结构完全一样,结构体A采用默认的字节对齐,故sizeof(A)=8;而结构体B将字节对齐的字节数设为1,这时结构体的占用空间刚好等于结构体各成员长度之和,故sizeof(B)=5。

猜你喜欢

转载自blog.csdn.net/qq_28249373/article/details/76784475
今日推荐