【C++编程】字节对齐(内存对齐)

数据对齐 

1)平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2)硬件原因:经过内存对齐之后,CPU的内存访问速度大大提升。


1.  对齐原则:

【原则1】数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

【原则2】结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

【原则3】结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。

备注:数组成员按长度按数组类型长度计算,如char t[9],在第1步中数据自身长度按1算,累加结构体时长度为9;第2步中,找最大数据长度时,如果结构体T有复杂类型成员A,该A成员的长度为该复杂类型成员A的最大成员长度。

小结:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。


 【注意(对齐位数跟处理器位数和编译器都有关)VS, VC等编译器默认是#pragma pack(8),所以测试我们的规则会正常;注意gcc默认是#pragma pack(4),并且gcc只支持1,2,4对齐。套用三原则里计算的对齐值是不能大于#pragma pack指定的n值。

2. 自然对齐存放变量的地址要是该变量数据类型大小的整数倍。如:存放int型数据的地址一定要是4的倍数,存放short型数据的地址一定要是2的倍数。

 

3. 改变缺省的对界条件(指定对界):

  • 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
  • 使用伪指令#pragma pack(),取消自定义字节对齐方式。

举例一

例1:

#pragma pack(1)
struct AA
{
    int a;   //长度4 < 1 按1对齐;偏移量为0;存放位置区间[0,3]
    char b;  //长度1 = 1 按1对齐;偏移量为4;存放位置区间[4]
    short c; //长度2 > 1 按1对齐;偏移量为5;存放位置区间[5,6]
    char d;  //长度1 = 1 按1对齐;偏移量为6;存放位置区间[7]
    //整体存放在[0~7]位置区间中,共八个字节。
};
#pragma pack()
#pragma pack(1)
struct AA 
{
    int a;    //长度4 < 1 按1对齐;偏移量为0;存放位置区间[0,3]
    char b;   //长度1 = 1 按1对齐;偏移量为4;存放位置区间[4]
    short c;  //长度2 > 1 按1对齐;偏移量为5;存放位置区间[5,6]
    char d;   //长度1 = 1 按1对齐;偏移量为6;存放位置区间[7]
    //整体存放在[0~7]位置区间中,共八个字节。
};
#pragma pack()

 结果:8个字节

 

例2:

#pragma pack(2)
struct AA
{
    int a;    //长度4 > 2 按2对齐;偏移量为0;存放位置区间[0,3]
    char b;   //长度1 < 2 按1对齐;偏移量为4;存放位置区间[4]
    short c;  //长度2 = 2 按2对齐;偏移量要提升到2的倍数6;存放位置区间[6,7]
    char d;   //长度1 < 2 按1对齐;偏移量为7;存放位置区间[8];共九个字节
};
#pragma pack()

 结果:10个字节

 

例3:

#pragma pack(4)
struct AA 
{
    int a;    //长度4 = 4 按4对齐;偏移量为0;存放位置区间[0,3]
    char b;   //长度1 < 4 按1对齐;偏移量为4;存放位置区间[4]
    short c;  //长度2 < 4 按2对齐;偏移量要提升到2的倍数6;存放位置区间[6,7]
    char d;   //长度1 < 4 按1对齐;偏移量为7;存放位置区间[8];总大小为9
};
#pragma pack()

 结果:12个字节

 

例4:

#pragma pack(8)
struct AA 
{
    int a;    //长度4 < 8 按4对齐;偏移量为0;存放位置区间[0,3]
    char b;   //长度1 < 8 按1对齐;偏移量为4;存放位置区间[4]
    short c;  //长度2 < 8 按2对齐;偏移量要提升到2的倍数6;存放位置区间[6,7]
    char d;   //长度1 < 8 按1对齐;偏移量为7;存放位置区间[8],总大小为9
};
#pragma pack()

 结果:12个字节  

 

例5:

struct EE   //8个字节对齐
{
    int a;       //长度4 < 8 按4对齐;偏移量为0;存放位置区间[0,3]
    char b;      //长度1 < 8 按1对齐;偏移量为4;存放位置区间[4]
    short c;     //长度2 < 8 按2对齐;偏移量由5提升到6;存放位置区间[6,7]

    struct FF    //结构体内部最大元素为int,由于偏移量为8刚好是4的整数倍,所以从8开始存放接下来的struct FF
    {
        int a1;      //长度4 < 8 按4对齐;偏移量为8;存放位置区间[8,11]
        char b1;     //长度1 < 8 按1对齐;偏移量为12;存放位置区间[12]
        short c1;    //长度2 < 8 按2对齐;偏移量为13,提升到2的倍数14;存放位置区间[14,15]
        char d1;     //长度1 < 8 按1对齐;偏移量为16;存放位置区间[16]
    };               //整体对齐系数 = min((max(int,short,char), 8) = 4,将内存大小由17补齐到4的整数倍20
   
    char d;          //长度1 < 8 按1对齐;偏移量为21;存放位置区间[21]
                    //整体对齐系数 = min((max(int,short,char), 8) = 4,将内存大小由21补齐到4的整数倍24
};

 

struct B 
{
    char e[2];       //长度1 < 8 按2对齐;偏移量为0;存放位置区间[0,1]
    short h;         //长度2 < 8 按2对齐;偏移量为2;存放位置区间[2,3]
                    //结构体内部最大元素为double,偏移量为4,提升到8,所以从8开始存放接下来的struct A
    struct A 
    {
        int a;       //长度4 < 8 按4对齐;偏移量为8;存放位置区间[8,11]
        double b;    //长度8 = 8 按8对齐;偏移量为12,提升到16;存放位置区间16,23]
        float c;     //长度4 < 8,按4对齐;偏移量为24,存放位置区间[24,27]
    };
    //整体对齐系数 = min((max(int,double,float), 8) = 8,将内存大小由28补齐到8的整数倍32
};

举例二

代码1:

#include 
typedef struct
{
    int aa1;    //4个字节对齐 1111  
    char bb1;   //1个字节对齐 1  
    short cc1;  //2个字节对齐 011  
    char dd1;   //1个字节对齐 1  
}testlength1;
int length1 =  sizeof (testlength1);  //4个字节对齐,占用字节1111 1011 1000,length = 12

typedef struct
{
    char bb2;   //1个字节对齐 1  
    int aa2;    //4个字节对齐 01111  
    short cc2;  //2个字节对齐 11  
    char dd2;   //1个字节对齐 1  
}
testlength2; 
int length2 =  sizeof (testlength2);  //4个字节对齐,占用字节1011  1111 1000,length = 12

typedef struct
{
    char bb3;    //1个字节对齐 1  
    char dd3;    //1个字节对齐 1  
    int aa3;     //4个字节对齐 001111  
    short cc23;  //2个字节对齐 11  
}testlength3;
int length3 =  sizeof (testlength3);  //4个字节对齐,占用字节1100 1111 1100,length = 12

typedef struct
{
    char bb4;   //1个字节对齐 1  
    char dd4;   //1个字节对齐 1  
    short cc4;  //2个字节对齐 11  
    int aa4;    //4个字节对齐 1111  
} testlength4;
int length4 =  sizeof (testlength4);  //4个字节对齐,占用字节1111 1111,length = 8

int main( void )
{
    printf ( "length1 = %d.\n" ,length1);
    printf ( "length2 = %d.\n" , length2);
    printf ( "length3 = %d.\n" , length3);
    printf ( "length4 = %d.\n" , length4);
    return 0;
} 

VS2017输出结果:

 

 代码2:

#include 
#pragma pack(2)

typedef  struct
{
    int aa1;    //2个字节对齐 1111  
    char bb1;   //1个字节对齐 1  
    short cc1;  //2个字节对齐 011  
    char dd1;   //1个字节对齐 1  
} testlength1;
int length1 =  sizeof (testlength1);  //2个字节对齐,占用字节11 11 10 11 10,length = 10

typedef  struct
{
    char bb2;     //1个字节对齐 1
    int aa2;      //2个字节对齐 01111  
    short cc2;    //2个字节对齐 11  
    char dd2;     //1个字节对齐 1  
} testlength2;
int length2 =  sizeof (testlength2);  //2个字节对齐,占用字节10 11 11 11 10,length = 10

typedef  struct
{
    char bb3;    //1个字节对齐 1  
    char dd3;    //1个字节对齐 1  
    int aa3;     //2个字节对齐 11 11  
    short cc23;  //2个字节对齐 11  
}testlength3;
int length3 =  sizeof (testlength3);  //2个字节对齐,占用字节11 11 11 11,length = 8

typedef  struct
{
    char bb4;   //1个字节对齐 1  
    char dd4;   //1个字节对齐 1  
    short cc4;  //2个字节对齐 11  
    int aa4;    //2个字节对齐 11 11 
}testlength4;
int length4 =  sizeof (testlength4);  //2个字节对齐,占用字节11 11 11 11,length = 8

int main( void )
{
    printf( "length1 = %d.\n" , length1);
    printf( "length2 = %d.\n" , length2);
    printf( "length3 = %d.\n" , length3);
    printf( "length4 = %d.\n" , length4);
    return 0;
}

VS2017输出结果:

  

代码3:

#include
using namespace std;

typedef struct bb
{
    int id;              //[0]....[3]
    double weight;       //[8].....[15]      原则1
    float height;        //[16]..[19],总长要为8的整数倍,补齐[20]...[23]     原则3
}BB;

typedef struct aa
{
    char name[2];     //[0],[1]
    int id;          //[4]...[7]          原则1
    double score;     //[8]....[15]    
    short grade;      //[16],[17]        
    BB b;             //[24]......[47]          原则2
}AA;

int main()
{
    AA a;
    cout <<  sizeof (a) <<  " " <<  sizeof (BB) << endl;
    return 0;
}

VS2017输出结果: 48 24

 

代码4:

#include
using namespace std;

#pragma pack(2)
typedef struct bb
{
    int id;             
    double weight;      
    float height;       
}BB;

typedef struct aa
{
    char name[2];    
    int id;        
    double score;    
    short grade;         
    BB b;           
}AA;

int main()
{
    AA a;
    cout <<  sizeof (a) <<  " " <<  sizeof (BB) << endl;
    return 0;
}

VS2017输出结果:32 16  

 

参考资料

1. 5分钟搞定内存字节对齐
2.  快速理解字节对齐问题
3.  关于面试题中结构体内存对齐计算总结

 

 

 

posted @ 2018-07-03 12:29  苏格拉底的落泪  阅读(8374)  评论(0编辑  收藏  举报