内存对齐(成员涉及基本变量和位域,不涉及虚函数、虚基类)

时间:2022-12-12 16:35:07

转载于百度百科: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字节,这里都采用其编译器下默认的对齐系数