c语言结构体字节对齐规则,C++ 字节对齐
原文:http://blog.csdn.net/hyljqr/article/details/500899
结构尺寸
先看一个结构:
结构S1
{
char c;
int I;
};
根据VC6中的默认设置,sizeof(s1)的结果为8。
我们先来看看sizeof的定义。——sizeof的结果等于对象或类型占用的内存字节数。让我们来看看S1的内存分配。
S1 s1={ a ,0x ffffffff };
定义好以上变量后,添加断点,运行程序,观察s1所在的内存。你发现了什么?
以我的VC6.0(sp6)为例。s1的地址是0x0012FF78,其数据内容如下:
0012FF78: 61 CC CC CC FF FF FF FF
你发现了什么?为什么中间有一个3字节的CC?看看MSDN上的说明:
当应用于结构类型或变量时,sizeof返回实际大小,其中可能包括为对齐而插入的填充字节。
所以,这就是传说中的字节对齐!出现了一个重要的话题。
为什么需要字节对齐?计算机组成原理告诉我们,这样有助于加快计算机的检索速度,否则需要更多的指令周期。为此编译器会默认处理结构(其实其他地方的数据变量也一样),这样宽度为2 (short等)的基本数据类型。)位于可被2整除的地址,宽度为4的基本数据类型(int等。)位于可被4整除的地址,依此类推。这样,可能需要在两个数之间添加填充字节,因此整个结构的sizeof值增加。
让我们交换一下char和int在S1的位置:
结构S2
{
int I;
char c;
};
让我们看看sizeof(S2)的结果是什么。为什么还是8?那我们来看看内存。原来成员c后面还有3个填充字节,这是为什么?别担心。这里是规则的总结。
字节对齐的细节与编译器实现有关,但一般来说,要满足三个标准:
1)结构变量的第一个地址可以被它最宽的基本类型成员的大小整除;
2)结构的每个成员相对于结构头地址的偏移量是成员大小的整数倍,必要时编译器会加上内部加法);成员之间的字节数;
3)结构的总大小是结构的最宽基本类型成员大小的整数倍。如有必要,编译器将在最后一个成员后添加尾随填充。
对于上述准则,有几点需要说明:
1)我没说结构成员的地址是其大小的整数倍,但是怎么谈偏移量呢?因为第一点存在,所以我们只能考虑构件的偏移量,这个问题想起来很简单。想想为什么。
结构成员相对于结构头地址的偏移量可以通过宏offsetof()获得,它也在stddef.h中定义如下:
#定义offsetof(s,m) (size_t) (((s *)0)- m)
比如想得到C在S2的偏移量,方法是size_t pos=offsetof(S2,C);//pos等于4
2)基本类型是指前面提到的char、short、int、float、double等内置数据类型,这里的“数据宽度”是指其sizeof的大小。因为一个结构的成员可以是复合类型,比如另一个结构,所以在寻找最宽的基本类型成员时,我们应该包括复合类型成员的子成员,而不是把复合成员作为一个整体来对待。但是,在确定复合类型成员的偏移位置时,复合类型被视为一个整体。在这里描述有点拗口,想想也有点抓痒。举个例子(具体数值还是VC6,以后不解释):
结构S3
{
char c1
S1的;
char c2
};
S1最宽简单成员的类型是int,S3在考虑最宽简单成员时“分散”了S1,所以S3最宽简单成员是int。这样,S3定义的变量的存储空间的第一个地址需要能被4整除,整个sizeof(S3)的值也要能被4整除。c1的偏移量为0,S的偏移量,此时S是一个整体。作为一个结构变量,它也满足前面的三个条件,所以它的大小是8,偏移量是4,所以c1和S之间需要三个填充字节,而c2和S之间不需要,所以c2的偏移量是12,把c2的大小算作13,13不能被4整除,所以最后要加三个填充字节。sizeof(S3)的最终值是16。
通过以上描述,我们可以得到一个公式:
结构的大小等于最后一个成员的偏移量加上其大小再加上末尾的填充字节数,即:
sizeof( struct )=offsetof(最后一项)sizeof(最后一项)sizeof( tr
不良填充)
至此,朋友们应该对sizeof结构有了全新的认识,但也不要高兴得太早。影响sizeof的一个重要参数还没有提到,那就是编译器的pack指令。它用于调整结构的对齐。不同的编译器在名称和用法上略有不同。在VC6中通过#pragma pack实现,也可以直接修改/Zp编译开关。#pragma pack的基本用法是#pragma pack( n),其中n是字节对齐数,其值为1、2、4、8、16,默认值为8。如果该值小于结构成员的sizeof值,则该成员的偏移量应该基于该值,即结构成员的偏移量应该取两者的最小值,公式如下:
offsetof( item )=min( n,sizeof( item))
再看看这个例子:
#pragma pack(push) //在堆栈上保存当前的包设置。
#pragma pack(2)//必须在结构定义之前使用。
结构S1
{
char c;
int I;
};
结构S3
{
char c1
S1的;
char c2
};
#pragma pack(pop) //恢复以前的包设置
计算sizeof(S1)时,min(2,sizeof(i))的值为2,所以I的偏移量为2,加上sizeof(i)等于6,可以被2整除,所以整个S1的大小为6。同样,对于sizeof(S3),s的偏移量是2,c2的偏移量是8,加上sizeof(c2)等于9,不能被2整除。添加一个填充字节,因此sizeof(S3)等于10。
现在,朋友们可以轻松地呼吸了,)
还需要注意的是,“空结构”(不包括数据成员)的大小不是0,而是1。想象一下一个“无空间”变量是如何被寻址的,以及两个不同的“空结构”变量是如何被区分的。因此,必须存储“空结构”变量,这样编译器只能分配一个字节的空间供其占用。如下所示:
结构S5 { };
sizeof(S5);//结果是1
具有位域结构的Sizeof:
如前所述,位字段的成员不能只接受sizeof值。这里要讨论的是包含位域的结构的sizeof,只是考虑到它的特殊性才列出来的。
C99规定int、unsigned int和bool可以作为位域类型,但几乎所有编译器都对此进行了扩展,允许其他类型的存在。
使用位域的主要目的是压缩存储,其一般规则如下:
1)如果相邻位字段的类型相同,并且它们的位宽之和小于该类型的sizeof size,则下面的字
该段将存储在前一个字段的旁边,直到无法容纳为止;
2)如果相邻的位字段属于同一类型,但它们的位宽之和大于该类型的sizeof size,则下面的字
该段将从新的存储单元开始,其偏移量是其类型大小的整数倍;
3)如果相邻位域的类型不同,每个编译器的具体实现也不同,VC6采用的是未压缩的方式。
型,Dev-C采用压缩模式;
4)如果非比特字段字段散布在比特字段字段之间,则不执行压缩;
5)整个结构的总大小是最宽基本类型的成员大小的整数倍。
让我们看一个例子。
示例1:
结构BF1
{
char f1:3;
char F2:4;
char F3:5;
};
它的内存布局是:
_ _ f1 _ _ _ _ F2 _ _ _ _ _ _ F3 _ _ _ _ _ _ _ _ _ _ _ _ _ _
________________________________
字段类型为char,第1个字节只能存放f1和f2,所以f2被压缩到第1个字节,而f3只能
可以从下一个字节开始。因此,sizeof(BF1)的结果是2。
示例2:
结构BF2
{
char f1:3;
短F2:4;
char F3:5;
};
由于相邻位域的类型不同,它的sizeof在VC6中是6,在DEV-C中是2。
示例3:
结构BF3
{
char f1:3;
char f2
char F3:5;
};
非位字段穿插其中,不会造成压缩。在VC6和Dev-C中获得的大小都是3。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。