數據對齊
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()
結果: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 <stdio.h>
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 <stdio.h>
#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<iostream>
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<iostream>
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. 關於面試題中結構體內存對齊計算總結
