内存对齐问题

1.内存数据对齐的原因:

    无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。

2.数据对齐原则:

    1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节, 则要从4的整数倍地址开始存储),基本类型不包括struct/class/uinon。

    2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部"最宽基本类型成员"的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)。

    3、收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的"最宽基本类型成员"的整数倍.不足的要补齐.(基本类型不包括struct/class/uinon)。

    4、sizeof(union),以结构里面size最大元素为union的size,因为在某一时刻,union只有一个成员真正存储于该地址。

3.具体实例:

#include <iostream>   
using namespace std;  
union a  
{  
 int a_int1;  
 double a_double;    //8字节   
 int a_int2;  
 char ch;  
                     //由原则4,a的大小为8   
};  
struct b  
{  
 a a1;              //[0]...[7] ,   
 char y;            //[8]   
                    //由原则3,"最宽基本类型成员"为a中的double,从而b的大小为8的整数倍,所以b的大小为16   
};  
class c  
{  
 int c_double;      //[0]...[4]   
 b b1;              //[8]...[23]   原则2:与b的内部的最大元素的整数倍开始存储   
 char ch;           //[24]   
                    //由原则3,"最宽基本类型成员"为b中,即b里面a的double,c的大小为8的整数倍,所以c的大小为32   
};  
struct usc  
{  
    char ch;        //[0]   
    b b2;           //[8]...[23]   原则2:与b的内部的最大元素的整数倍开始存储   
    a a2;           //[24]...[31]      
    c c2;           //[32]...[63]   
    char ch1;       //[64]   
                    //"最宽基本类型成员"为a中的double 或者 即b里面a的double 或者 c里面的b里面的double   
                    //由原则3,usc的大小为8的整数倍,所以usc的大小为72   
};  
void main()  
{  
    cout<<"sizeof(a)= "<<sizeof(a)<<endl;  
    cout<<"sizeof(b)= "<<sizeof(b)<<endl;    
    cout<<"sizeof(c)= "<<sizeof(c)<<endl;                                               
    cout<<"sizeof(usc)= "<<sizeof(usc)<<endl;    
}  
 

总结:

       从“struct/class以及union内存对齐原则”可以得出:在struct/class/union中定义变量时,长度小的变量先定义,长度大的变量后定义,可以节省内存。

4.#pragma pack()

在代码前加一句#pragma pack(1),#pragma pack(1)是告诉编译器,所有的对齐都按照1的整数倍对齐,换句话说就是没有对齐规则.
 

5.位域(特例)

#include<iostream>
using namespace std;
#pragma pack(1)
struct A 
{ 
 //unsigned short i:8;
 char t:4; //位域的意思:表示t占4个位
 char k:4;
 unsigned long m; 
 //char k:4; 
 unsigned short i:8;
 //unsigned short j:8;
} ;
#pragma pack()
int main(){
	A b;
	cout<<sizeof(b)<<endl;
	system("pause");
	return 1;
}

 不考虑边界对齐,大小为7,tk他俩占第一个字节,i占第二个字节,m从四字节开始占四个字节,问题是m为什么不从第三字节开始:是因为第二三字节的short被划分为2个字节类型的位模式了,m改为unsigned short m:8;就可以从第三字节开始了。

同理:

#include<iostream>
using namespace std;
#pragma pack(1)
struct A 
{ 
 //unsigned short i:8;
 char t:4; 
 //char k:4;
 unsigned long m; 
 char k:4; 
 unsigned short i:8;
 //unsigned short j:8;
} ;
#pragma pack()
int main(){
	A b;
	cout<<sizeof(b)<<endl;
	system("pause");
	return 1;
}

 输出就是8.

#include<iostream>
using namespace std;
#pragma pack(1)
struct A 
{ 
 //unsigned short i:8;
 char t:4; 
 char k:4;
 unsigned long m; 
 //char k:4; 
 unsigned short i:8;
 unsigned short j:8;
} ;
#pragma pack()
int main(){
	A b;
	cout<<sizeof(b)<<endl;
	system("pause");
	return 1;
}

 输出是7.

#include<iostream>
using namespace std;
#pragma pack(1)
struct A 
{ 
 unsigned short i:8;
 char t:4; 
 char k:4;
 unsigned long m; 
 //char k:4; 
 //unsigned short i:8;
 unsigned short j:8;
} ;
#pragma pack()
int main(){
	A b;
	cout<<sizeof(b)<<endl;
	system("pause");
	return 1;
}

 输出是9.

总结:位域将原类型占用的字节分成相应的位数,只有后面是相同的类型才能继续占用没有使用的位数;如果是不同的类型就需要按前面类型原有的字节来往后顺延到本类型开始的字节位置。

补充:

  指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。

  #pragma pack规定的对齐长度,实际使用的规则是:

      结构,联合,或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的   数值和结构体的自然对齐长度中比较小的那个进行。就是说,当#pragma pack的值等于或超过所有数据成员长度的时     候,这个值的大小将不产生任何效果。 

实例:具体解释:

#pragma pack(4)

  class TestB

  {

  public:

    int aa; //第一个成员,放在[0,3]偏移的位置,

    char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。

    short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。

    char c; //第四个,自身长为1,放在[8]的位置。

  };

这个类实际占据的内存空间是9字节

类之间的对齐,是按照类内部最大的成员的长度,和#pragma pack规定的值之中较小的一个对齐的。

所以这个例子中,类之间对齐的长度是min(sizeof(int),4),也就是4。

9按照4字节圆整的结果是12,所以sizeof(TestB)是12。

 #pragma pack(2)

     class TestB

  {

  public:

    int aa; //第一个成员,放在[0,3]偏移的位置,

    char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。

    short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。

    char c; //第四个,自身长为1,放在[8]的位置。

  };

//可以看出,上面的位置完全没有变化,只是类之间改为按2字节对齐,9按2圆整的结果是10。

//所以 sizeof(TestB)是10。

#pragma pack(push) //保存对齐状态
#pragma pack(2)

struct A 
{ 
     char    b; 
     int      a; 
     short c; 
}; 
#pragma pack(pop) //恢复对齐状态

 int main()
{

    cout << sizeof(A)
 
分析:
这里指定了对齐长度为2;
b的对齐长度为1字节,存放地址:0x0000
a的对齐长度为2字节,存放地址:0x0002 ~ 0x0005;//数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
c的对齐长度为2字节,   存放地址:0x0006 ~ 0x0007;
A的对齐方式为2,(这里8是2的倍数,因此不用补0)
所以sizeof(A)为2的整数倍:8
 

猜你喜欢

转载自1527zhaobin.iteye.com/blog/1584589