类的sizeof大小

#pragma pack(n) 字节对齐用法详解

对 齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但 是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32 位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节 进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。
对齐的实现

通常,我们写程序的时候,不需要考虑对齐问题。编译器会替我们选择时候目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。

但是,正因为我们一般不需要关心这个问题,所以因为编辑器对数据存放做了对齐,而我们不了解的话,常常会对一些问题感到迷惑。最常见的就是struct数据结构的sizeof结果,出乎意料。为此,我们需要对对齐算法所了解。

作用:
指定结构体、联合以及类成员的packing alignment;

语法:
#pragma pack( [show] | [push | pop] [, identifier], n )

说明:
1,pack提供数据声明级别的控制,对定义不起作用;
2,调用pack时不指定参数,n将被设成默认值;
3,一旦改变数据类型的alignment,直接效果就是占用memory的减少,但是performance会下降;

语法具体分析:
1,show:可选参数;显示当前packing aligment的字节数,以warning message的形式被显示;
2,push:可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;
3,pop:可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略;
4,identifier:可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;
5,n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。

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

对齐的算法:
由于各个平台和编译器的不同,现以本人使用的gcc version 3.2.2编译器(32位x86平台)为例子,来讨论编译器对struct数据结构中的各成员如何进行对齐的。

总结:对任一变量a,根据min{sizeof( a), value}来对齐。

相同的对齐方式下,结构体内部数据定义的顺序不同,结构体整体占据内存空间也不同,如下:
设结构体如下定义:

struct A
{
int a;
char b;
short c;
};

其内存分布:

a a a a

b _ c c   //c是2字节对齐,其始址要能被2整除
结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个。所以A用到的空间应该是7字节。但是因为编译器要对数据成员在空间上进行对齐。所以使用sizeof(strcut A)值为8
现在把该结构体调整成员变量的顺序。

struct B
{
char b;
int a;
short c;
};

其内存分布:

b _ _ _

a a a a    //4字节对齐,故要始址要能被4整除

c c _ _
这时候同样是总共7个字节的变量,但是sizeof(struct B)的值却是12

下面我们使用预编译指令#progma pack (value)来告诉编译器,使用我们指定的对齐值来取代缺省的。

#progma pack (2) /*指定按2字节对齐,等价于
#pragma pack(push,2)*/
struct C
{
char b;
int a;
short c;
};
#progma pack () /*取消指定对齐,恢复缺省对齐,等价于
#pragma pack(pop)*/
sizeof(struct C)值是8

其内存分布:

b _

a a  //min{sizeof( a),value}=min{4,2}=2字节对齐。始址要能被2整除

a a

c c


修改对齐值为1:
#progma pack (1) /*指定按1字节对齐
*/
struct D
{
char b;
int a;
short c;
};
#progma pack () /*取消指定对齐,恢复缺省对齐
*/
sizeof(struct D)值为7

其内存分布:

b

a //min{sizeof( a),value}=min{4,1}=1字节对齐。始址要能被1整除

a

a

a

c //min{sizeof( c),value}=min{2,1}=1字节对齐。始址要能被1整除

c



对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。

这里面有四个概念值:
1.数据类型自身的对齐值:就是上面交代的基本数据类型的自身对齐值。

2.指定对齐值:#progma pack (value)时的指定对齐值value。

3.结构体或者类的自身对齐值:其数据成员中自身对齐值最大的那个值。

4.数据成员、结构体和类的
有效对齐值:自身对齐值和指定对齐值value中小的那个值。
有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0". 而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍,结合下面例子理解)。这样就不能理解上面的几个例子的值了。
例子分析:

分析例子B;

struct B
{
char b;
int a;
short c;
};
假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值
默认为4

第一个成员变量b的自身对齐值是1,比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.

第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4,所以只能存放在起始地址为0x00040x0007这四个连续的字节空间中,符合0x0004%4=0, 且紧靠第一个变量。

第三个变量c,自身对齐值为2,所以有效对齐值也是2,可以存放在0x00080x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的都是B内容。

再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求,0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A0x000B也为结构体B所占用。故B从0x0000到0x000B共有12个字节,sizeof(struct B)=12;

同理,分析上面例子C:

#progma pack (2) /*指定按2字节对齐
*/
struct C
{
char b;
int a;
short c;
};
#progma pack () /*取消指定对齐,恢复缺省对齐
*/
第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1=0;

第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。

第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放在0x0006、0x0007中,符合0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。

又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.

struct SS

{

    float f;

    char p;

    int a[ 3];

};

 

sizeof( SS)= 20。是以int的4字节对齐,而不是以int a[3]的12字节对齐。

即:

f f f f

p _ _ _

a a a a

a a a a

a a a a

 

--------------------

char ch[ 7];//sizeof(ch)= 7;

void F( char a[ 7])

{

  sizeof( a)=4,因为函数传参时,数组退化为指针,sizeof(指针)=4。

}

 

----------

struct A

{

  char ch[ 5];

};//sizeof( A)=5: ch ch ch ch ch

 

struct B

{  

  A a;

};//sizeof( B)=5

 

struct C

{  

  char b;

  A a;

};//sizeof( C)=6: b a a a a a

 

struct D

{  

  char b;

  A a;

  char c[ 3];

};//sizeof( D)=9: b a a a a a c c c

 

struct E

{  

  char b;

  A a;

  int i;

};//sizeof( E)=12:

b a a a

a a _ _

i i i i

 

struct F

{  

  char b[3];

  A a;

  int i;

};//sizeof( F)=12:

b b b a

a a a a

i i i i

 

struct G

{  

  char b[4];

  A a;

  int i;

};//sizeof( G)=16:

b b b b

a a a a

a _ _ _

i i i i

 

struct H

{  

  char b[ 3];

  double d;

  A a;

};//sizeof( H)=16:

b b b b _ _ _ _

d d d d d d d d

a a a a a _ _ _

 

-------------

以上代码表示,类A的对齐方式以其基本数据成员中最大的对齐(也即有效对齐值)max(为1),当类C中包含另一个类A时,C的对齐是A的最大对齐(1:char型)和C其他数据成员的最大值,而不是sizeof(A)和C其他数据成员对齐的最大值。

如上面代码中,类A总是以char对齐(1字节),C的对齐字节数为max{ char, A的对齐数}={ char, char}=1,D的对齐字节数为max{ char, A的对齐数, char}={ char, char, char}=1,E的对齐字节数为max{ char, A的对齐数, int}={ char, char, int}=4。而且sizeof(类型)为数据成员中最大的长度的整数倍(如A、B、C、D为1的整数倍;E、F、G为4的整数倍;H为8的整数倍)。

 

注:数组仍以单个元素的基本类型为主,如class CC{ int p[ 12]; }; 则CC以int(即4字节)对齐,而不是以p数组的大小48字节对齐。

 

而下面代码中,类Q中的类P是以4字节(int)对齐。

struct P

{

  int i;

};//sizeof( P)=4:i i i i

 

struct Q

{  

  char b;

  A a;

};

//sizeof( Q)=8:

b _ _ _

a a a a

 

struct R

{  

  char b;

  A a;

  char c;

};//sizeof( R)=12:

b _ _ _

a a a a

c _ _ _

 

------------------------

注:如果类G中包含类F,则被包含的类F的始址须能被F的有效对齐值整除。如:

class F

{

public:

    char a;

    int b;

    char c;

};

class G

{

public:

    char s;

    F t;

    char u;

};

F的有效对齐值是4,F内存为:

a _ _ _

b b b b

c _ _ _

 

G内存为:

s _ _ _

a _ _ _ 

b b b b

c _ _ _

u _ _ _

 

上面红色标记的3行是F的空间,G中类F的始址要能被F的有效对齐值4整除。注意,G中的C是一个整体,不能将F中的a放入第一行(如前2行为:

s a _ _ 

b b b b

这会改变a与b之间的间隔,即破坏了F的结构,当通过偏移来访问F中的b时会出错),也不能将u放入第4行。G中F的内部结构不能改变,否则如果要访问G中的F时会出错。

原文:http://blog.chinaunix.net/space.php?uid=9950859&do=blog&id=99143

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值