字节对齐

来源:互联网 发布:淘宝什么东西销量最高 编辑:程序博客网 时间:2024/06/06 00:22

1简述

现代计算机中内存空间都是按照byte划分的,从理论上讲对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。而所谓的内存对齐,就是数据在内存地址的起点应尽量对齐(是某个值的公倍数),而不是凌乱不堪的。一般我们把8bit看成一个byte,用byte来做存储的单位,其实也是一种内存对齐的机制。

内存对齐的目的很简单,这样有利于提供访问的速度(特别是在栈中)。对于对齐的内存地址,处理器访问一次即可得到,而未对齐的地址,可能需要访问两次才行。

通常,我们写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。但是,正因为我们一般不需要关心这个问题,所以因为编辑器对数据存放做了对齐,而我们不了解的话,常常会对一些问题感到迷惑。最常见的就是struct数据结构的sizeof结果。下面我们需要对对齐算法进行了解:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,其中单位是字节。这里面有四个概念值:

(1)数据类型自身的对齐值:就是上面交代的基本数据类型的自身对齐值。

(2)指定对齐值:#pragma pack (value)时的指定对齐值value

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

(4)数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小的那个值。

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

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。所以从0x00000x0009存放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求, 0x00090x0000=10字节,(102)%40。所以0x0000A0x000B也为结构体B所占用。故B0x00000x000B共有12个字节,sizeof(struct B)=12。同理,分析下面例子:#pragma pack (2) /*指定按2字节对齐*/

struct C {

char b;

int a;

short c;

};

#pragma pack () /*取消指定对齐,恢复缺省对齐*/

第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C0x0000开始,那么b存放在0x0000,符合0x0000%1= 0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x00020x00030x00040x0005四个连续 字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放在0x00060x0007中,符合0x0006%2=0。所以从0x00000x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以 C的有效对齐值为2。又8%2=0,C只占用0x00000x0007的八个字节。所以sizeof(struct C)=8

所以结构体内成员的对齐规则如下:假设第一个成员的offset0,则之后成员的对齐系数是系统的对齐系数(可以通过pragma pack(x)来指定)和自身长度两者间较小的值。比如,系统的对齐系数是4,而char的长度是1,那么char就按1对齐。而int的长度是4,于系统对齐系数一致,则按4对齐。

从上面这个规则,我们可以看到一个有趣的问题,在结构体内,成员的摆放顺序不同,结构体所占空间是不同的。比如,

typedef struct ex{

         char c1;

         char c2;

    int c3;

}ve;

typedef struct ex{

     char c1;

     int c3;

 char c2;

}ve;

前者所占的空间是8,而后者所占空间是12。说完了c1,c2,c3的对齐,下面说ve的对齐。sizeof(ve)的值事实上并不是如我们分析的c1,c2,c3对齐完之后所占空间的累加,它自身也是需要对齐的。结构体的对齐规则如下:取其成员中占用空间最长的值,我们的例子中是int类型,值是4,然后跟系统的对齐系数比较,取较小值为ve的对齐系数。如果我们在结构体2中,语句int c3;后面再加一句char temp;我们可以算出成员所占的空间是9,而ve所占的空间是12

2练习

1.写出下面的结果:

struct

{

          int a;

          char b;

}sa;

struct

{

         int a;

         char b;

         double c;

}sb;

struct

{

         int a;

         double c;

         char b;

}sc;

答:输出结果:sizeof(sa)=8, sizeof(sb)=16, sizeof(sc)=24;

2.写出下面的结果

struct

{

     int a;

     char b, c, d, e,;

} sa1;

struct

{

     int a;

     char b;

     int c;

} sa2;

答:输出结果:sizeof(sa1)=8, sizeof(sa2)=12;

3.写出下面的结果

struct

{

     int a;

int d;

     double c;

     char b;

} sc1;

struct

{

     int a;

     double c;

} sc2;

答:输出结果:sizeof(sc1)=24, sizeof(sc2)=16;

4.写出下面的结果

struct   // packing is 8

{

        int a;

        char b;

} sa;

#pragma pack(1) // packing is now 1

struct

{

        int a;

        char b;

        double c;

} sb;

#pragma pack() // packing is 8

struct

{

        int a;

        double c;

        char b;

} sc;

int main(void)

{

        cout << "sizeof(sa) =" << sizeof(sa) << endl;

        cout << "sizeof(sb) =" << sizeof(sb) << endl;

        cout << "sizeof(sc) =" << sizeof(sc) << endl;

        return 0;

}

答:输出结果:sizeof(sa)=8,sizeof(sb)=13,sizeof(sc)=24

5.为什么要内存对齐?

答:编译器自动对齐的原因:为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。

原创粉丝点击