Chinaunix首页 | 论坛 | 博客
  • 博客访问: 801673
  • 博文数量: 104
  • 博客积分: 915
  • 博客等级: 下士
  • 技术积分: 2171
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-24 21:34
文章分类

全部博文(104)

文章存档

2018年(4)

2015年(14)

2014年(9)

2013年(56)

2012年(21)

分类: C/C++

2012-05-31 15:35:47

字节对齐的一些总结!

1、X86的字节对齐规则

基本的数据类型就有如下规则:

char —— 单字节对齐,A MOD 1 = 0;可为任意的奇/偶值
short —— 双字节对齐,A MOD 2 = 0;偶地址
int —— 四字节对齐,A MOD 4 = 0;地址最后一位为:4,8,C
double —— 八字节对齐, A MOD 8 = 0;地址最后一位为:0,8

2、堆栈的对齐布局

先用VC6运行一个例子:
#include
int main(int argc, char* argv[])
{
char c0;
char c1;
char c2;
printf(”%p\n%p\n%p\n”,&c0,&c1,&c2);
return 0;
}
在Debug模式下,得到类似如下的结果(具体地址值可能不同):
0012FF7C
0012FF78
0012FF74

这个结果的每个地址,都符合1字节对齐,MOD 1 = 0。不过,也符合四字节对齐,MOD 4 = 0。
堆栈理论上只需要3个字节,这里却用了12个字节。

因为VC6编译器默认对堆栈采用了4字节对齐原则。所以在不进行任何优化的情况下,一个char对象也需要占据4个字节空间。

4字节对齐总是兼容单/双字节对齐方式的。

我们可以修改上一个例子:
#include
int main(int argc, char* argv[])
{
char c0;
char c1;
double d0;
char c2;
printf(”%p\n%p\n%p\n%p\n”,&c0,&d0,&c1,&c2);
return 0;
}

Debug模式下:
0012FF7C
0012FF6C
0012FF78
0012FF74
VC6将堆栈变量的位置做了偏移。double被放在了栈顶。这样做的好处是什么暂时不太清楚。

3、结构体/联合体的布局

struct/union中最大的那个alignment,而这个max(alignment),又取决于编译器的设置(用参数/Zpn或者代码中用 #pragma pack(n)),即自身的对齐要求和编译器设置对齐要求二者中的最小值。

4.1 简单的情况
例如:
#include “stdio.h”
struct SA
{
char g;
double k;
};

int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}

输出结果如下:
Size=16
Addr=0012FF70
VC6默认采用8字节对齐,double的对齐要求也是8字节。因此这里输出大小为16,地址按照规则需符合8字节对齐,最后四位为0000。

我们可以加上pack 限制
#include “stdio.h”
#pragma pack(4)
struct SA
{
char g;
double k;
};

int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}
则输出为:
Size=12
Addr=0012FF74
将默认的对齐边界设置为4,则成员g仅占用4个字节,因为k在4字节边界上对齐。由于SA的成员最大对齐值为4,所以整个结构体也4字节对齐。所以地址的最后4位可以被4整除。

4.2 嵌套的结构体成员
看一个例子:
#include “stdio.h”
struct SB
{
int c;
double k;
short b;
};

struct SA
{
char g;
SB b;
char k;
};

int main(int argc, char* 1498 argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}
输出为:
Size=40
Addr=0012FF58

从这个例子可以看出嵌套结构体的规则其实也很简单。首先找到SA中成员的最大对齐边界,由于b是结构体类型,因此b的对齐边界取决于SB中的成员最大对齐边界:8。
因此,SA中的成员8字节对齐。
由于SB中的成员也是8字节对齐的,所以大小为8*5=40。
而整个变量a的地址也要符合8字节对齐要求。

再看看用了pack(4)的情况:
#include “stdio.h”
#pragma pack(4)
struct SB
{
int c;
double k;
short b;
};

struct SA
{
char g;
SB b;
char k;
};

int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}
按照分析,SB的最大对齐边界:4,所以SA的对齐边界也是4,这个结果应该输出:
Size=24
Addr=0012FF68

注意pack(n)的限制,作用域从起始位置开始。例如:
#include “stdio.h”
struct SB
{
int c;
double k;
short b;
};

#pragma pack(4)
struct SA
{
char g;
SB b;
char k;
};

int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}
SB按照8字节对齐,SA按照4字节对齐,其成员b整体上满足4字节对齐即可。因此大小是8*3(SB大小)+8(SA的成员g和k的大小):
Size=32
Addr=0012FF60

再看更复杂的例子:
#include “stdio.h”
#pragma pack(4)
struct SB
{
int c;
double k;
short b;
};

#pragma pack(2)
struct SA
{
char g;
SB b;
char k;
};

int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}
SB的对齐边界为4,因此大小为16,SA的对齐边界为2(最小值),因此大小为16+4=20:
Size=20
Addr=0012FF6C

再来看看成员变量位置的颠倒会给struct带来什么结果?
#include “stdio.h”
struct SB
{
int c;
short b;
double k;
};
struct SA
{
char g;
SB b;
char k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}
运行结果如下:
Size=32
Addr=0012FF60

变量a的大小变成了32,因为SB中的double成员k的声明被放在了最后。而int成员c和short成员b各占4字节,就可以保证k处于8字节对齐状态。
因此,SB的大小为16。再加上SA的另外两个成员:g和k分别占用8个字节,所以得到的Size就为32。
同样,地址还是符合8字节对齐边界的要求。

从这个例子也可以看出,通过合理安排结构体成员的声明顺序,可以减少其占用内存的大小。
阅读(3357) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~