數據對齊
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. 關於面試題中結構體內存對齊計算總結