原本觉的c语言学的还不错,今天看了一下c语言的有关书籍,看到求结构体大小的例子,心想这不是很简单吗,不就是把所有成员的大小加起来就行了,其实远没我想的简单,看来c语言还要继续学习哈哈。
先看一个实例
typedef struct
{
char c;
int b;
short d;
}A;
这个结构体的大小是多少了,我做的是char占1字节,int占4字节,short占2字节,1 + 4 + 2 = 7. 总共占7字节。哈哈完全错误。
正确结果
12
为什么是12呢?不急我们在看一个实例
typedef struct
{
char c;
short d;
int b;
}B;
这个大小又是多少呢,相信细心的你一定会问,这和上个结构体不是一样的吗,只是在定义的时候把 int b; 和 short d; 对调了一下。应该结果和上个一样吧。当初我也是这样想的,结果哈哈又错了。
正确答案
8
这是为什么呢,相信你们已经迫不及待想知道答案了吧。那我们就来一一揭晓啦。
这里涉及到内存字节对齐的问题。从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排列,而不是简单地顺序排列,这就是内存对齐。
在C99标准中,对于内存对齐的细节没有作过多的描述,具体的实现交由编译器去处理,所以在不同的编译环境下,内存对齐可能略有不同,但是对齐的最基本原则是一致的,对于结构体的字节对齐主要有下面两点:
-
结构体每个成员相对结构体首地址的偏移量(offset)是对齐参数的整 数倍,如有需要会在成员之间填充字节。编译器在为结构体成员开辟空间时,首先检查预开辟空间的地址相对于结构体首地址的偏移量是否为对齐参数的整数倍,若是,则存放该成员;若不是,则填充若干字节,以达到整数倍的要求。
-
结构体变量所占空间的大小是对齐参数大小的整数倍。如有需要会在最后一个成员末尾填充若干字节使得所占空间大小是对齐参数大小的整数倍。
在windows下各种类型的变量的自身对齐参数就是该类型变量所占字节数的大小,除了变量的自身对齐参数外,还有一个对齐参数,就是每个编译器默认的对齐参数#pragma pack(n),这个值可以通过代码去设定,如果没有设定,则取系统的默认值.n的取值可以为1、2、4、8等一般默认情况下为8。
了解了这2个概念之后,可以理解上面2条原则了。对于第一条原则,每个变量相对于结构体的首地址的偏移量必须是对齐参数的整数倍,这句话中的对齐参数是取每个变量自身对齐参数和系统默认对齐参数#pragma pack(n)中较小的一个。举个简单的例子,比如在结构体A中有变量int a,a的自身对齐参数为4(环境为windows/DEV),而DEV默认的对齐参数为8,取较小者,则对于a,它相对于结构体A的起始地址的偏移量必须是4的倍数。
对于第二条原则,结构体变量所占空间的大小是对齐参数的整数倍。这句话中的对齐参数有点复杂,它是取结构体中所有变量的对齐参数的最大值和系统默认对齐参数#pragma pack(n)比较,较小者作为对齐参数。举个例子假如在结构体A中先后定义了两个变量int a;double b;对于变量a,它的自身对齐参数为4,而#pragma pack(n)值默认为8,则a的对齐参数为4;b的自身对齐参数为8,而#pragma pack(n)的默认值为8,则b的对齐参数为8。即结构体内的每个遍历也要取自身的对齐参数和默认对齐参数比较,取较小者作为这个变量的对齐参数。由于a的最终对齐参数为4,b的最终对齐参数为8,那么两者较大者是8,然后再拿8和#pragma pack(n)作比较,取较小者作为对齐参数,也就是8,即意味着结构体最终的大小必须能被8整除。
下面通过图解让我们更深刻的了解结构体的所占空间分布
例1
|char | | | | 4字节
|int |int |int |int | 4字节
|short|short| | | 4字节
故一共占12字节
例2
|char | |short|short| 4字节
|int |int |int |int | 4字节
故一共8字节
是不是想跃跃欲试呢,那我们就在来几个实例
typedef struct
{
char c;
short d;
static int a;
}C;
这个结果又是多少呢?
答案
4
图解
例3
|char | | 2字节
|short|short| 2字节
咦我们定义的静态变量a去哪了,是不是图错了啊。图没有错,正因为我们的a是静态变量,而静态数据成员的存放位置与结构体实例的存储地址无关(注意只有在C++中结构体中才能含有静态数据成员,而C中结构体中是不允许含有静态数据成员的)。a是单独存放在静态数据区的,因此用siezof计算其大小时没有将a所占的空间计算进来。
是不是还不过瘾再来一个。
typedef struct
{
double b;
int c;
}D;
typedef struct
{
bool a;
D d;
double b;
int c;
}E;
在默认对齐参数下pragma pack(8)下结果是(上述实例也是)
D 16
E 40
相信结构体D所占字节数为16大家应该很清楚,不过这是在对齐参数下pragma pack(8)下的结果,当pragma pack(4)的时候结果应该会12,感兴趣的可以尝试一下。
我们重点来看一下E所占字节的大小。
对于变量a,其自身对齐参数为1,#pragma pack(n)为8,则a的最终对齐参数为1,为它分配1字节的空间,它相对于结构体起始地址的偏移量为0,能被1整除;
对于d,它的自身对齐参数为8(对于结构体变量,它的自身对齐参数为它里面各个变量最终对齐参数的最大值),#pragma pack(n)为8,所以d的最终对齐参数为8,接下来的地址相对于结构体起始地址的偏移量为1,不能被8整除,所以需要在a后面填充7字节达到8,为其分配16字节的空间;
对于变量b,它的自身对齐参数为8,#pragma pack(n)的默认值为8,则b的最终对齐参数为8,接下来的地址相对于结构体起始地址的偏移量为24,能被8整除,再为b分配8字节的空间;
对于变量c,它的自身对齐参数为4,#pragma pack(n)的默认值为8,则c的最终对齐参数为4,接下来相对于结构体其实地址的偏移量为32,能够被4整除,所以直接为c分配4字节的空间。
此时结构体所占字节数为1+7+16+8+4=36字节。
对于整个结构体来说,各个变量的最终对齐参数为1,8,8,4,最大值为8,#pragma pack(n)默认值为8,所以最终结构体的大小必须是8的倍数,因此需要在最后面填充4字节达到32字节。其存储如下:
|bool|-----------------------------------| 8字节
|--------------------D-------------------| 8字节
|--------------------D-------------------| 8字节
|------------------double----------------| 8字节
|---------int--------|-------------------| 8字节
共40字节