[TOC]
什么是字節(jié)對齊(可以跳過)
現(xiàn)代計算機中內(nèi)存空間都是按照字節(jié)(byte)劃分的都哭,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始照卦,但實際情況是在訪問特定變量的時候經(jīng)常在特定的內(nèi)存地址訪問儡蔓,這就需要各類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序地一個接一個地排放,這就是對齊.
字節(jié)對齊的好處(可以跳過)
為了提高效率,計算機從內(nèi)存中取數(shù)據(jù)是按照一個固定長度的斧吐。以32位機為例,它每次取32個位仲器,也就是4個字節(jié)(每字節(jié)8個位)煤率。字節(jié)對齊有什么好處?以int型數(shù)據(jù)為例乏冀,如果它在內(nèi)存中存放的位置按4字節(jié)對齊蝶糯,也就是說1個int的數(shù)據(jù)全部落在計算機一次取數(shù)的區(qū)間內(nèi),那么只需要取一次就可以了.
如何對齊
通常辆沦,我們寫程序的時候昼捍,不需要考慮對齊問題,編譯器會替我們選擇適合目標平臺的對齊策略肢扯。但是妒茬,正因為我們一般不需要關心這個問題,所以鹃彻,如果編輯器對數(shù)據(jù)存放做了對齊郊闯,而我們不了解的話,常常會對一些問題感到迷惑蛛株。最常見的就是struct數(shù)據(jù)結構的sizeof結果,例如:
typedef struct
{
char member1;
short member2;
int member3;
}Family;
//打印長度
NSLog(@"Family size is %zd",sizeof(Family));
//輸出結果
2016-07-22 15:34:29.081 Study[14587:5575156] Family size is 8
//下面我們更換一下成員變量位置,看看有什么效果
typedef struct
{
char member1;
int member3;
short member2;
}Family;
//打印長度
NSLog(@"Family size is %zd",sizeof(Family));
//輸出結果
2016-07-22 15:36:25.126 Study[14591:5575689] Family size is 12
那么問題來了,兩個結構體的成員變量只是改變了下順序育拨,為什么占用的內(nèi)存大小不同呢谨履?
對齊原則:
- char 偏移量必須為sizeof(char) 即1的倍數(shù),可以任意地址開始存儲
- short 偏移量必須為sizeof(short) 即2的倍數(shù),只能從0,2,4...等2的倍數(shù)的地址開始存儲
- int 偏移量必須為sizeof(int) 即4的倍數(shù),只能從0,4,8...等4的倍數(shù)的地址開始存儲
- float 偏移量必須為sizeof(float) 即4的倍數(shù),只能從0,4,8...等4的倍數(shù)的地址開始存儲
- double 偏移量必須為sizeof(double)即8的倍數(shù),只能從0,8,16...等地址開始存儲
根據(jù)以上原則,我們來分析:
typedef struct
{
char member1;
short member2;
int member3;
}Family;
- 這個結構體:member1占一個字節(jié),即 存儲位置為 0
- member2占兩個字節(jié),根據(jù)上面原則,開始存儲地址應該是2的倍數(shù),即 2~3
- member3占4個字節(jié),根據(jù)原則,開始存儲地址是4的倍數(shù),即 4~7
-
總共占用了0 ~ 7 共8個字節(jié).
內(nèi)存存儲圖為:
下面分析:
typedef struct
{
char member1;
int member3;
short member2;
}Family;
- 這個結構體:member1占一個字節(jié),即 0
- member2占4個字節(jié),根據(jù)上面原則,開始存儲地址應該是4的倍數(shù),即 4~7
-
member3占2個字節(jié),根據(jù)上面原則,開始存儲地址是2的倍數(shù),即 8 ~ 9
總共占用了0 ~ 9 應該是10個字節(jié),但為什么實際卻是12個字節(jié)呢 ?
因為默認對齊方式是4字節(jié)(至于為什么,往下看),也就是說,總長度必須是4的倍數(shù),因此長度既要大于10,還要是4的倍數(shù),那就是12了.
內(nèi)存結構圖為:
結構體如何設定字節(jié)對齊
當未明確指定時,以結構體中最長的成員的長度為其有效值,上面的兩個結構體都是int類型最長,也就是4字節(jié)對齊
結構體作為成員:如果一個結構里有某些結構體成員,則結構體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲.(struct a里存有struct b,b里有char,int ,double等元素,那b應該從8的整數(shù)倍開始存儲.)
結構體的總大小,也就是sizeof的結果,必須是其內(nèi)部最大成員的整數(shù)倍.不足的要補齊.
當用#pragma pack(n)指定時熬丧,以n和結構體中最長的成員的長度中較小者為其值.
用#pragma pack()為還原字節(jié)對齊為默認值.
attribute ((packed)) 1字節(jié)對齊,此時結構體的長度就是各成員變量長度之和.
例如:
pragma pack 的用法
struct A {
int a;
char b;
short c;
};
struct B {
char b;
int a;
short c;
};
#pragma pack (2) /*指定按2字節(jié)對齊*/
struct C {
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊笋粟,恢復缺省對齊*/
#pragma pack (1) /*指定按1字節(jié)對齊*/
struct D {
char b;
int a;
short c;
};
#pragma pack ()/*取消指定對齊,恢復缺省對齊*/
//計算所占字節(jié)
int s1=sizeof(struct A);
int s2=sizeof(struct B);
int s3=sizeof(struct C);
int s4=sizeof(struct D);
printf("%d\n",s1);
printf("%d\n",s2);
printf("%d\n",s3);
printf("%d\n",s4);
//輸出結果:
8
12
8
7
attribute ((packed))的用法:
讓指定的結構結構體按照 1 字節(jié)對齊,例如:
//不加packed修飾
typedef struct {
char version;
int16_t sid;
int32_t len;
int64_t time;
} Header;
//計算長度
NSLog(@"size is %zd",sizeof(Header));
輸出結果為:
2016-07-22 11:53:47.728 Study[14378:5523450] size is 16
可以看出,默認系統(tǒng)是按照4字節(jié)對齊
//加packed修飾
typedef struct {
char version;
int16_t sid;
int32_t len;
int64_t time;
}__attribute__ ((packed)) Header;
//計算長度
NSLog(@"size is %zd",sizeof(Header));
輸出結果為:
2016-07-22 11:57:46.970 Study[14382:5524502] size is 15
用packed修飾后,變?yōu)?字節(jié)對齊,這個常用于與協(xié)議有關的網(wǎng)絡傳輸中.