编程中的字节对齐问题

在没有#pragma pack宏的情况下,要遵守下面的三个原则

1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。

2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)

3、收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐.

以下是实战:

typedef struct bb
{
 int id;             //[0]....[3]
 double weight;      //[8].....[15]      原则1
 float height;      //[16]..[19],总长要为8的整数倍,补齐[20]...[23]     原则3
}BB;

typedef struct aa
{
 char name[2];     //[0],[1]
 int  id;         //[4]...[7]          原则1

 double score;     //[8]....[15]    
 short grade;    //[16],[17]        
 BB b;             //[24]......[47]          原则2
}AA;

int main()
{
  AA a;
  cout<<sizeof(a)<<" "<<sizeof(BB)<<endl;
  return 0;
}

 

结果是

48 24
在包含有#pragma pack().的情况下
1,复杂类型中各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个类型的地址相同;
2,每个成员分别对齐,即每个成员按自己的方式对齐,并最小化长度;规则就是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐;
3,结构、联合或者类的数据成员,第一个放在偏移为0的地方;以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度两个中比较小的那个进行;也就是说,当#pragma pack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果;
4,复杂类型(如结构)整体的对齐是按照结构体中长度最大的数据成员和#pragma pack指定值之间较小的那个值进行;这样在成员是复杂类型时,可以最小化长度;
5,结构整体长度的计算必须取所用过的所有对齐参数的整数倍,不够补空字节;也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方;这样在处理数组时可以保证每一项都边界对齐;

更改c编译器的缺省字节对齐方式:
在缺省情况下,c编译器为每一个变量或数据单元按其自然对界条件分配空间;一般地可以通过下面的两种方法来改变缺省的对界条件:
方法一:
使用#pragma pack(n),指定c编译器按照n个字节对齐;
使用#pragma pack(),取消自定义字节对齐方式。
方法二:
__attribute(aligned(n)),让所作用的数据成员对齐在n字节的自然边界上;如果结构中有成员的长度大于n,则按照最大成员的长度来对齐;
__attribute((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

综上所述,下面给出例子并详细分析:

例子一:
#pragma pack(4)
class TestB
{
public:
int aa; //第一个成员,放在[0,3]偏移的位置,
  char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
  short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
  char c; //第四个,自身长为1,放在[8]的位置。
};
可见,此类实际占用的内存空间是9个字节。根据规则5,结构整体的对齐是min( sizeof( int ), pack_value ) = 4,所以sizeof( TestB ) = 12;

例子二:
#pragma pack(2)
class TestB
{
public:
int aa; //第一个成员,放在[0,3]偏移的位置,
  char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
  short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
  char c; //第四个,自身长为1,放在[8]的位置。
};
可见结果与例子一相同,各个成员的位置没有改变,但是此时结构整体的对齐是min( sizeof( int ), pack_value ) = 2,所以sizeof( TestB ) = 10;

例子三:
#pragma pack(4)
class TestC
{
public:
char a; //第一个成员,放在[0]偏移的位置,
  short b; //第二个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[2,3]的位置。
  char c; //第三个,自身长为1,放在[4]的位置。
};
整个类的实际内存消耗是5个字节,整体按照min( sizeof( short ), 4 ) = 2对齐,所以结果是sizeof( TestC ) = 6;

例子四:
struct Test
{
char x1; //第一个成员,放在[0]位置,
short x2; //第二个成员,自身长度为2,按2字节对齐,所以放在偏移[2,3]的位置,
float x3; //第三个成员,自身长度为4,按4字节对齐,所以放在偏移[4,7]的位置,
char x4; //第四个陈冠,自身长度为1,按1字节对齐,所以放在偏移[8]的位置,
};
所以整个结构体的实际内存消耗是9个字节,但考虑到结构整体的对齐是4个字节,所以整个结构占用的空间是12个字节。

例子五:
#pragma pack(8)
struct s1
{
short a; //第一个,放在[0,1]位置,
long b; //第二个,自身长度为4,按min(4, 8) = 4对齐,所以放在[4,7]位置
};
所以结构体的实际内存消耗是8个字节,结构体的对齐是min( sizeof( long ), pack_value ) = 4字节,所以整个结构占用的空间是8个字节。
struct s2
{
char c; //第一个,放在[0]位置,
s1 d; //第二个,根据规则四,对齐是min( 4, pack_value ) = 4字节,所以放在[4,11]位置,
long long e; //第三个,自身长度为8字节,所以按8字节对齐,所以放在[16,23]位置,
};
所以实际内存消耗是24自己,整体对齐方式是8字节,所以整个结构占用的空间是24字节。
#pragma pack()
所以:
sizeof(s2) = 24, s2的c后面是空了3个字节接着是d。
reference:http://blog.csdn.net/jamesf1982/article/details/4375719 和  http://blog.csdn.net/hairetz/article/details/4084088
最后介绍下结构体中的位域。
字面理解位域就是说某些数据元素并不需要占据一整个字节,只需要占据几位,例如数字8,就只需要占据一个字节的四位即可表示。所谓位域,就是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。
例如下面的定义:
struct
{
 int a:8;
 int b:2;
 int c:6;
}A; 
位域a占8位,位域b占2位,位域c占6位,总共占据两个字节。
关于位域的对齐,有如下几点:


1. 如果相邻位域类型相同,位宽之和小于类型的sizeof大小,则后面的字段紧邻前一个字段存储,直到不能容纳为止;
2. 如果相邻位域类型相同,位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3. 如果相邻位域类型不同,则vc6采取不压缩方式,dev-c++ 和GCC都采取压缩方式。依然满足结构体内存对齐三个原则中的原则1,在不压缩方式下,如果前一个位域类型有填充,后面的位域类型和前面的位域类型不相同,则填充的区域不能存放放后面的位域,需另开辟空间;而在压缩方式下,填充的区域如果可以放下后者位域,则存放,放不下的情况下再另开辟空间。
4.一个位域必须存储在同一个字节中,不能跨字节;
5.如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。
 struct
    {
          char a:2;
          int b:4;
          int c:4;
    }A;
在不压缩条件下:char类型占据一个字节,而a占用其中两位;根据原则1,b的类型int占据四个字节,应该从4的整数倍处开始存放,所以char后应该填充三个字节,这三个字节虽然能够容纳b,但是必须另开空间,再开辟四个字节的空间存放int,而b占据四个字节,后面c的类型和b的类型相同,所以紧邻b存储,占据四个字节。所以在vc下,sizeof(A) = 8;
压缩条件下:char类型占据一个字节,而a占用其中两位;根据原则1,b的类型int占据四个字节,应该从4的整数倍处开始存放,所以char后应该填充三个字节,这三个字节能够容纳b和c,因此不需要重新开辟空间,直接在这三个字节上存储,所以sizeof(A) = 4;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值