内存对齐是一个老生常谈的话题,也是各大小公司笔试中出现频率比较高的题目,今天正好看到论坛上大家讨论得热火朝天,我也凑一下热闹,做一个总结,如有错误,请指教!
1 为什么会存在内存对齐的问题?
要搞清楚内存对齐这个繁琐的细节问题,必须首先理解为什么会存在内存对齐的问题。
内存对齐从根本上说是一个以牺牲空间换时间的策略,因为在cpu寻址时,以32bit地址总线为例,一条指令最多从存储器中读取4个字节,例如:
MOV EAX, [0] #把数据段基地址处4个字节复制到EAX中
另外一点,在cpu对连续数据访问的过程中,让数据和1/2/4/8/16字节的内存边界对齐时,可以提高效率。因此,当访问一个多字节数据时,就可能存在数据跨越两个或多个边界的情况,这意味着cpu必须经过两次寻址才能得到数据,效率上大打折扣。这就是内存对齐的问题。
2 编译器对内存对齐的处理
既然可以让数据和1/2/4/8/16字节的内存边界,那么谁去执行具体安排数据呢?显然这是编译器的职责,编译器可以通过传递命令行参数或在代码中嵌入预编译指令来设置这个具体值。如在vc6中设置/Zp[1/2/4/8/16]或者在代码中加入:
#pragma pack(4)
struct A
{
......
} A;
#pragma pack()
3 对齐的准则
明白了原理,再理解对齐的准则就不难了:
数据的地址必须是其大小或编译器设置对齐值n的整数倍,具体取两者的最小值。
4 实例
求下列结构体的大小:
struct A
{
char a;
double b;
} A;
struct B
{
char a;
double b;
char c;
} B;
struct C
{
char a;
double b;
char c;
int d;
} C;
struct D
{
char a;
double b;
char c;
int d;
double e;
} D;
在windows vc6默认的环境下,默认是8字节对齐的,A,B,C的大小分别为16, 24, 24, 32。如果理解了对齐的准则,相信你很容易就能得到这个结果。
; Line 19
mov BYTE PTR _k$[ebp], 1 ;a
; Line 20
mov DWORD PTR _k$[ebp+8], 0 ;b
mov DWORD PTR _k$[ebp+12], 1072693248 ; 3ff00000H
; Line 21
mov BYTE PTR _k$[ebp+16], 2 ;c
; Line 22
mov DWORD PTR _k$[ebp+20], 3 ;d
; Line 23
mov DWORD PTR _k$[ebp+24], 0 ; e
mov DWORD PTR _k$[ebp+28], 1074266112 ; 40080000H
上面是D的汇编代码,可以看到结构体中每个元素的偏移量,有助于对内存对齐的理解。
接着:
#pragma pack(4)
struct A
{
char a;
double b;
} A;
struct B
{
char a;
double b;
char c;
} B;
struct C
{
char a;
double b;
char c;
int d;
} C;
struct D
{
char a;
double b;
char c;
int d;
double e;
} D;
#pragma pack()
现在A,B,C,D的大小分别为12, 16, 20, 28,再看看D的汇编代码:
; Line 19
mov BYTE PTR _k$[ebp], 1
; Line 20
mov DWORD PTR _k$[ebp+4], 0
mov DWORD PTR _k$[ebp+8], 1072693248 ; 3ff00000H
; Line 21
mov BYTE PTR _k$[ebp+12], 2
; Line 22
mov DWORD PTR _k$[ebp+16], 3
; Line 23
mov DWORD PTR _k$[ebp+20], 0
mov DWORD PTR _k$[ebp+24], 1074266112 ; 40080000H