分享

Windows和Linux下的字节对齐(转载)

 紫火神兵 2012-10-25
1. 重要概念

内存对齐:系统对基本类型数据在内存中存放的位置都有限制,它们会要求这些数据的首地址的值是某个数K的倍数。

对齐模数:结合内存对齐的概念,这个k值就是对齐模数。

注意:对齐模数的具体取值与编译器有关。

对其要求严格强度:当一种类型的对齐模数S与另一种类型的对齐模数T的比值大于1时,我们就称类型S要求比T严格(强),称T比S宽松(弱)。

内存对齐优点:简化处理器与内存之间传输系统的设计,提升读取数据的速度。所以,如果想要提升性能,那么所有的程序数据都应该尽可能地对其。

填充区:是结构体字段满足内存对齐要求而额外分配给结构体的空间。

ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部填充区大小之和。

ANSI C规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松,但可以更严格。

结构体和共用体的对齐模数就是该结构体中最严格类型的对齐模数。

 


2. 不同编译器的内存对齐规则

测试环境:

Windows7        Visual Studio2010  10.00

Ubuntu10.04    GCC gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3

2.1 VC/VS下的结构体

默认对齐规则:任何基本数据类型T的对齐模数就是T的大小,即sizeof(T)。

在VC/VS中,变量对齐规则如下:

char(8-bit)                    在字节边界上对齐

short(16-bit)                在双字节边界上对齐

int and long(32-bit)     在4字节边界上对齐

float(32-bit)                 在4字节边界上对齐

double(32-bit)             在8字节边界上对齐

下面是VC/VS与GCC共同默认的情况:

struct x                        与该结构体中最大对齐模数的类型相同

union x                        与该共用体中最大对齐模数的类型相同

 

1) 按照上面的规则,我们先来分析一下下面的一个结构体:

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

它在内存中的大致布局如下:

内存布局

由于a是单字节对齐,所以可以防止在任何位置。而b是双字节对齐,不能放在奇数地址上,所以不能紧接着放在a的后面,而是先要在a后面填充一个字节空间,然后才是b的空间。这样a和b共同占用了4个字节,最后c占用了4个字节的空间,由于c的对齐模数是该结构体中最大的对齐模数,a、填充区域再加b,刚好满4个字节,此时不再需要新的填充区域,因此整个结构体的大小就是1+1+2+4=8。

下面是各字段在内存中的地址以及最终该结构体所占用的空间:

char a:   0046FEF4
short b: 0046FEF6
int c:      0046FEF8
sizeof(struct st1) = 8

 

2) 交换一下顺序,结果就会不同:

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

其在内存中的布局如下:

内存对齐

c需要放在4字节边界上,也就是相对于起始地址偏移量为4或者4的倍数的位置上放置,现在a在起始位置,a后面需要填充3个区域才能开始放c。当c放好后,需要放b,b占用两个字节,那此时是否需要在b后面加上填充区域呢?

C标准规定,任何类型(包括自定义结构体类型)的数据所占用空间的大小一定等于一个单独的该类型数据的大小乘以数据元素的个数。换句话说,就是各个元素之间不会有空隙的。如果b之后不填充那两个字节,那么假如有一个struct st2类型的数组,它在内存中的布局将不满足上述要求,因为第二个元素的字段a是紧接着b开始的,这样最终元素的实际空间将小于按照规定得出的空间大小。

下面是各字段在内存中的地址以及最终该结构体所占用的空间:

char a:   0048FE4C
short b: 0048FE50
int c:      0048FE54
sizeof(struct st2) = 12

 

3) 如何就能断定,在VC/VS下默认的对齐模数就是最大的字段类型的对齐模数呢?经过上面的两个例子,下面的结构体应该很容易得到结果了

struct st3 {
    char       a;
    double   b ;
};

下面是各字段在内存中的地址以及最终该结构体所占用的空间:

char a:     0051FDD4
double b: 0051FDDC
sizeof(struct st3) = 16

可以清楚的发现,a后面填充了7个字节的空间,这是由于该结构体将8作为其对齐模数了。后面我会将GCC对齐规则展示。

 

4)如果结构体中有数组,则其对齐模数仍然按照单个元素的对齐模数来算:

struct st3 {
    char    a;
    char    b[8];
    double    c;
};

b数组类型是char,所以其对齐模数是1,可以作为a的填充,将前7个元素填充到a之后,最后一个元素放到下一个8字节区间,需要填充7位,接着放c。由此,该结构体最后的大小为24。

下面是各字段在内存中的地址以及最终该结构体所占用的空间:

char a:      0022F78C
char b[7]: 0022F78D
double c:  0022F79C
sizeof(struct st3) = 24

换成int类型数组原理一样:

struct st3 {
    char        a;
    int           b[2];
    double    c;
};

下面是各字段在内存中的地址以及最终该结构体所占用的空间:

char a:    0053F82C
int b[2]:  0053F830
double c: 0053F83C
sizeof(struct st3) = 24

 

5) 对于包含有其他结构体或者共用体的复杂结构体,且看下面的例子:

struct st4 {
    char            a;
    int               b;
    struct st3    c;
};

先分析一下,这个结构体应该占多少字节呢?显然,对齐模数要求最严格的是c,那么c该怎么算呢?是16吗?如果是16,那么结果是否应该是32?非也。

其实c的大小的确是16,但是c的对齐模数是8,按照规定,st4种的对齐模数便是8。因此a与b凑齐8个字节即可。因此最终st4所占用字节数为24。

其在内存中的布局如下:

内存对齐

下面是各字段在内存中的地址以及最终该结构体所占用的空间:

char a:         0016FD34
int b:            0016FD38
struct st3 c: 0016FD3C
sizeof(struct st4) = 24

若该结构体包含有共用体,计算方法与包含结构体成员情况类似,不同之处仅仅在于共用体成员的大小计算方法不同。共用体大小的计算方法看2.2部分。

 

2.2 VC/VS下的共用体

共用体的结构与结构体非常类似,不同点在于前者是所有成员共享同一段内存空间,而后者则单独分配内存空间。二者的对齐规则是相同的, 只不过在选取存储区域的时候按照最大对齐模数来寻找最大所需存储区。

1)先来看一个简单的例子

union un0 {
    char        a;
    double    b;
};

在这个共用体中,最大的对齐模数是8,而b刚好占用8个字节,所以该共用体最后的大小就是8。

char a:    002EFA5C
double b: 002EFA5C
sizeof(union un0): 8

可以看到共用体所有成员均是从同一地址开始的——共享同一存储区。

 

2)对于数组情况,则不能像结构体中那样看单元所占用空间大小了,而是要看数组总共占用的内存大小,因为共用体最终是要寻找一块可以容纳其最大的成员的内存空间。

union st2 {
    char    a;
    int      b[2];
};

对齐模数是4,b占用8个字节,因此该共用体应该为8。由于各成员的起始地址均相同,所以以后的例子不再列举个成员地址。

 

3)包含有其他共用体或者结构体的情况

不论包含的是结构体还是共用体,都需要计算该成员所需内存空间,结构体需要的内存计算方法在前面已经提到过,共用体的大小与其最大成员占用空间大小一致。结构体需要计算所有成员占用内存空间,而共用体只需要计算最大成员的内存空间即可。

union un2 {
    char    a;
    int      b[2];
};

union un4 {
    short          a;
    union un2   b;
};

un4的最大对齐模数与union un2相同,为4,而b的大小为8,因此这个共用体最后大小也为8。

struct st2 {
    char      a;
    double  b;
    int         c;
};

union un2 {
    char           a;
    struct st2   b;
    int              c;
};

sizeof(struct st2)=24; sizeof(union un2)=24

 

2.3 GCC下的结构体

对齐规则:(K代表对齐模数,T代表基本数据类型)

<=2:K=sizeof(T);

>2:K=4。

即小于等于2的时候,按照类型本身大小来算,而大于2的类型一律将4作为对齐模数。

1)为了验证该规则,并与VC/VS做一个对比,将VC/VS下测试的例子在GCC下测试:

struct ms1 {
    char        a;
    double    c;
};

如果按照8字节对齐,那么a之后需要填充7个字节,也就是c的起始地址应该是从下一个8字节区间开始,那最终结果应该是16,我们来看看测试结果:

char a:     0xbff49654
double c: 0xbff49658
sizeof(struct ms1): 12

发现c是从下一个4字节区间开始的,上述对齐规则得证。

当sizeof(T)<2时:

struct ms3 {
    char    a;
    char    b[2];
};

有:

char a:    0xbfff7c11
short b:   0xbfff7c12
sizeof(struct ms3): 3

当sizeof(T)=2时:

struct ms4 {
    char    a;
    short    b;
};

有:

char a: 0xbff71f1c
short b: 0xbff71f1e
sizeof(struct ms4): 4

 

2)根据上面的规则,下面的结构体应该很容易判断

struct ms1 {
    char        a;
    short       b;
    double    c;
};

因为a小于2字节,因此需要填充1个字节,而b刚好2个字节,c是8个字节,按规定其对齐模数为4,因此“拆”成2半,这样的话,a与b共同凑成4个字节,此结构体最终的小就是12。

内存布局如下:

内存对齐

下面是各字段在内存中的地址以及最终该结构体所占用的空间:

char a:     0xbfb6d984
short b:   0xbfb6d986
double c: 0xbfb6d988
sizeof(struct ms1): 12

 

3)带有字符数组的结构体

struct ms1 {
    double   a;
    int          b;
    char       c;
    char       d[9];
};

内存对齐

下面是各字段在内存中的地址以及最终该结构体所占用的空间:

double a:  0xbfd7c2f0
int b:         0xbfd7c2f8
char c:       0xbfd7c2fc
char d[9]:  0xbfd7c2fd
sizeof(struct ms1): 24

 

4)包含其他结构体或者共用体的情况下,依然按照最初设定的规则,超过4字节,一律按照4字节处理,因此最终复合结构体的大小也很容易算出,这里就不再举例了。

 

GCC下的共用体

在GCC下,共用体依然遵守同结构体相同的对齐规则。所有数据类型依然以2字节为界限。

经过上面的讨论,GCC下的共用体应该不用多说了。在此我只总结性的讲一下如何计算,例子就不举了。

首先要计算看哪个成员的对齐模数最大,选取最大的对齐模数,然后看占用最大空间的成员的内存空间是否为该对齐模数的整数倍,若不是,填充若干字节直到达到该对齐模数的最小公倍数。

如果该共用体中含有结构体或者共用体成员,需要求得该成员的对齐模数。结构体和共用体的对齐模数与其自身成员的最大对齐模数相同。需要注意的是共用体所占用的内存为与其占用内存最大的成员的内存空间相同。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多