转载于百度百科:http://baike.baidu.com/view/4786260.htm
说明:在此文中,类中成员涉及基本变量和位域,不涉及虚函数、虚基类
内存对齐的定义:
内存对齐是编译器的管辖范围,编译器将程序中的每个数据单元安排在适当的位置上。对于大部分程序员来说,内存对齐对他们来说都应该是透明的。但是,C语言允许程序员干预内存对齐,如果你想了解更加底层的秘密,内存对齐对你就不应该再透明了。
内存对齐的原因:
(1) 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
(2) 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
内存对齐的规则:
每个编译器都有自己的默认对齐系数。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的对齐系数。注意,n只能取这几个值,如果n取其它值,系统将无视此对齐系数,而使用系统默认的值。
具体规则:
1、数据成员对齐规则:结构体(struct)或联合题(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
具体来说:
(1) 第一个成员的存放地址相对于相对于首地址的偏移地址为0。
(2) 其它成员的存放地址是按 min(#pragma pack指定的数值 , 该数据成员自身长度) 进行对齐,即找到能整除该最小值的最近偏移地址。
总结<1>:从单个成员上说,存放该变量的首地址 = 能除尽min(#pragma pack指定的数值 , 该数据成员自身长度) 的最近偏移地址的值。
2、 结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
根据原则2,可以得到结构体或联合体最终的占的内存大小
(1)最终的内存大小按min(#pragma pack指定的数值 ,结构(或联合)中最大数据成员长度) 对齐
(2)根据所有成员目前已经占有的空间大小Sum,找到比Sum值大而且能够整除较小值min值的值。
总结<2>:从整体上说,该类的大小等于比Sum值大而且能够整除较小值min值的值。
3、 结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
即,当#pragma pack的n值比任何数据成员的长度都大时,内存对齐可以直接忽略n值。
举例:
#pragma pack(4) class A { public: int a; /* int型长度4 = 4 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */ char b; /* char型长度1 < 4 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */ short c;/*short型长度2 < 4 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */ char d[6];/* char型长度1 < 4 按1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */ }; sizeof(A) = 16 数据成员对齐: 1、任一数据成员对齐系数 = min(n,该数据成员长度) 2、按照对齐系数对齐 数据成员对齐举例: 对于short c; 对齐系数 = min(4,2) = 2 初始的偏移地址为5 且 5%2 !=0,得出其偏移地址为6 整体对齐 整体对齐系数 = min(n,所有数据成员的最大长度) 数据成员对齐举例: 整体对齐系数 = min(n,max(int,char,short,char)) = 4; 对所有数据成员对齐后,结构体占的大小为14个字节, 而14%4!=0,此时需要找到离14最近且能够整除4的数 = 16
注意事项:
(1) 在微软的编译器中,结构体第一个变量的首地址是其最长基本类型成员的整数倍
(2) 在微软的编译器中,默认的对齐系数为8,即 #pragam pack(8)
(3) 在GCC的编译器中,对齐系数最大值只能为4,即使成员中有double变量。n只能取值1,2,4。
(4) 成员的对齐顺序是按变量在结构体中声明顺序进行的
(5) 如果在结构体中包含结构体成员变量,则先对结构体变量进行内存对齐,之后在对本结构体进行内存对齐
(6) 结构体总大小是包括填充字节在内的总大小
举例:系统XP,编译器VS2008
sizeof(char) = 1; sizeof(short) = 2; sizeof(int) = 4; sizeof(double) = 8;
举例 (1) 1字节对齐
#pragma pack(1) class A { public: int a; char b; short c; char d[6]; }; 输出结果:sizeof(A) = 13 分析过程: 1) 成员数据对齐 #pragma pack(1) class A { int a; /* int型,长度4 > 1 按1对齐;起始offset=0 0%1=0;存放位置区间[0,3] */ char b; /* char型,长度1 = 1 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */ short c; /* short型,长度2 > 1 按1对齐;起始offset=5 5%1=0;存放位置区间[5,6] */ char d[6]; /* char型,长度1 = 1 按1对齐;起始offset=7 7%1=0;存放位置区间[7,12] */ }; #pragma pack() 成员总大小=13 2) 整体对齐 整体对齐系数 = min((max(int,short,char), 1) = 1 整体大小(size)=(成员总大小) 按 (整体对齐系数) 圆整 = 13 /*13%1=0*/举例(2) 2字节对齐
#pragma pack(2) class A { public: int a; char b; short c; char d[6]; }; 输出结果:sizeof(A) = 14 分析过程: 1) 成员数据对齐 #pragma pack(2) class A { int a; /* int型长度4 > 2 按2对齐;起始offset=0 0%2=0;存放位置区间[0,3] */ char b; /* char型长度1 < 2 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */ short c; /* short型长度2 = 2 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */ char d[6]; /* char型长度1 < 2 按1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */ }; #pragma pack() 成员总大小=14 2) 整体对齐 整体对齐系数 = min((max(int,short,char), 2) = 2 整体大小(size)=(成员总大小) 按(整体对齐系数) 圆整 = 14 /* 14%2=0 */举例(3) 4字节对齐
#pragma pack(4) class A { public: int a; char b; short c; char d[6]; }; 输出结果:sizeof(A) = 16 分析过程: 1) 成员数据对齐 #pragma pack(4) class A { int a; /* int型,长度4 = 4 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */ char b; /* char型,长度1 < 4 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */ short c; /*short型, 长度2 < 4 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */ char d[6]; /* char型,长度1 < 4 按1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */ }; #pragma pack() 成员总大小=14 2) 整体对齐 整体对齐系数 = min((max(int,short,char), 4) = 4 整体大小(size)=(成员总大小)按(整体对齐系数) 圆整 = 16 /*16%4=0*/举例(4) 8字节对齐
#pragma pack(8) class A { public: int a; char b; short c; char d[6]; }; 输出结果:sizeof(A) = 16 分析过程: 1) 成员数据对齐 #pragma pack(8) class A { int a; /* int型,长度4 < 8 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */ char b; /* char型,长度1 < 8 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */ short c; /* short型,长度2 < 8 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */ char d[6]; /* char型,长度1 < 8 按1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */ }; #pragma pack() 成员总大小=14 2) 整体对齐 整体对齐系数 = min((max(int,short,char), 8) = 4 整体大小(size)=(成员总大小) 按 (整体对齐系数) 圆整 = 16 /*16%4=0*/举例( 5 ) 16字节对齐
#pragma pack(16) class A { public: int a; char b; short c; char d[6]; }; 输出结果:sizeof(A) = 16 分析过程: 1) 成员数据对齐 #pragma pack(16) class A { int a; /* int型,长度4 < 16 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */ char b; /* char型,长度1 < 16 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */ short c; /* short型,长度2 < 16 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */ char d[6]; /* char型,长度1 < 16 按1对齐;起始offset=8 8%1=0;存放位置区间[8,13] */ }; #pragma pack() 成员总大小=14 2) 整体对齐 整体对齐系数 = min((max(int,short,char), 16) = 4 整体大小(size)=(成员总大小) 按 (整体对齐系数) 圆整 = 16 /*16%4=0*/
说明:
什么是“圆整”?举例说明:如上面的8字节对齐中的“整体对齐”,整体大小=9 按 4 圆整 = 12
圆整的过程:从9开始每次加一,看是否能被4整除,这里9,10,11均不能被4整除,到12时可以,则圆整结束。
位域
如果结构体中含有位域(bit-field),规则如下:
(1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
(2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
(3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式:即不同位域字段仍存放在相同的位域类型字节中
换句话说,当相邻位域字段的类型不同时,VC把不同的位域字段分别找自己的空间存入,即不进行压缩。GCC把不同的位域字段都放入同一个位域类型字节中,从下一个字节开始存储,(注意,可不是把不同类型的位域放入同一个字节中)。
(4) 如果位域字段之间穿插着非位域字段,则不对位域进行压缩;
(5) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,且应尽量节省内存
举例:使用规则3
class A { public: char c:2; int i:4; }; 如果是规则3中的不压缩方式存储(VC) sizeof(A) = 8 如果是规则3中的压缩方式存储(gcc) sizeof(A) = 4分析:
如果是不压缩方式(VC):
成员遍历c放入第0个字节,而成员遍历i的偏移地址应该是4的整数倍,所以c成员后要填充3个字节,然后再开辟4个字节的空间作为int型,其中4位用来存放i,所以上面结构体在VC中所占空间为8个字节;
即,一个字节放c,中间填充三个字节,之后四个字节放i
如果是压缩方式(gcc):
成员遍历c放入第0个字节,而成员遍历i的偏移地址应该是4的整数倍,所以c成员后要填充3个字节。不同的是,如果填充的3个字节能容纳后面成员的位,则压缩到填充字节中,不能容纳,则要单独开辟空间。这里填充的三个字节能够容纳变量i,则在GCC或者Dev- C++中所占空间应该是4个字节。
举例:使用规则四
class A { public: char c:2; double d; int i:4; }; 在VC中,采用默认的对齐系数 = 8 sizeof(A) = 24 在gcc中,采用默认对齐系数 = 4 sizeof(A) = 16
相关说明:
(1) 定义结构体或类的时候,成员最好能从大到小来定义,那样能相对的省空间。
举例:
class A { double d; int i; char c; };在vc下或者gcc下,都是大小都是16字节,这里都采用其编译器下默认的对齐系数