iOS底層原理 02 : 結構體內存對齊原則

什么是內存對齊

元素是按照定義順序一個一個放到內存中去的,但并不是緊密排列的。從結構體存儲的首地址開始,每個元素放置到內存中時,它都會認為內存是按照自己的大?。ㄍǔK鼮?或8)來劃分的,因此元素放置的位置一定會在自己寬度的整數倍上開始,這就是所謂的內存對齊。

編譯器為程序中的每個“數據單元”安排在適當的位置上。C語言允許你干預“內存對齊”。如果你想了解更加底層的秘密,“內存對齊”對你就不應該再模糊了。

下圖是結構體在32bit和64bit環境下各基本數據類型所占的字節數:

基本數據類型所占的字節數.png

結構體內存對齊

接下來我們定義兩個struct,通過打印它們的sizeof()來探索一下其對齊的規律

struct LGHStruct01 {
    long  a;
    char b;
    double c;
    int d;
    short e;
}struct01;


struct LGHStruct02 {
    long  a;
    double b;
    int c;
    short d;
    char e;
}struct02;

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    NSLog(@"%lu-%lu",sizeof(struct01),sizeof(struct02));
        
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

// 打印的結果是: 32-24

我們可以看到兩個結構體其中定義的變量 以及變量類型都是一致的,唯一的區別是在于定義變量的順序不一致,為什么struct01,struct02所占的內存大小一個是32一個是24呢?接下來我們來了解一下內存對齊原則。

內存對齊規則

  • 原則一:數據成員對?規則:結構(struct)(或聯合(union))的數據成員,第?個數據成員放在offset為0的地?,以后每個數據成員存儲的起始位置要從該成員??或者成員的?成員??(只要該成員有?成員,?如說是數組,結構體等)的整數倍開始(?如int為4字節,則要從4的整數倍地址開始存
    儲。 min(當前開始的位置m n) m = 9 n = 4 (9 10 11 12)
  • 原則二:結構體作為成員:如果?個結構?有某些結構體成員,則結構體成員要從其內部最?元素??的整數倍地址開始存儲.(struct struct03?存有struct struct01,struct01?有long,char,int ,double等元素,那struct01應該從8的整數倍開始存儲.)
  • 原則三:收尾?作:結構體的總??,也就是sizeof的結果,.必須是當前結構體或者其嵌套的某個結構體成員的最大成員大小的整數倍.不?的要補?。
根據對齊原則,我們具體來分析struct01 、struct02結構體:
結構體MyStruct1 內存大小計算:
  • 變量a:占8個字節,從0開始,min(0,8),即放在下0-7存儲
  • 變量b:占1個字節,從8開始,min(8,1),8%1==0,即放在8存儲
  • 變量c:占 8 個字節,從 9 開始,min(9,8),9%8!=0,往后移直到min(16,8),所以從變量c放在16-23存儲
  • 變量d:占4字節,從24開始,min(24,4),24%4==0,即d放在24-27存儲
  • 變量e:占2字節,從28開始,min(28,2),28%2==0,即e放在28-29存儲

因此struct01需要的內存大小是30字節[0-29],而LGHStruct01中最大變量的字節數為8,所以 LGHStruct01 實際的內存大小必須是 8 的整數倍,30向上取整到32,主要是因為32是8的整數倍,所以 sizeof(LGHStruct01) = 32
示意圖如下:

LGHStruct01.png

結構體MyStruct02 內存大小計算:
  • 變量a:占8個字節,從0開始,min(0,8),即放在下0-7存儲
  • 變量b:占8個字節,從8開始,min(8,8),8%8==0,即放在8-15存儲
  • 變量c:占 4 個字節,從 16 開始,min(16,4),16%4==0,所以變量c放在16-19存儲
  • 變量d:占2字節,從20開始,min(20,2),20%2==0,即d放在20-21存儲
  • 變量e:占1字節,從22開始,min(22,1),22%1==0,即e放在22存儲

因此struct01需要的內存大小是23字節[0-22],而LGHStruct02中最大變量的字節數為8,所以 LGHStruct02 實際的內存大小必須是 8 的整數倍,23向上取整到24,主要是因為24是8的整數倍,所以 sizeof(LGHStruct02) = 24
示意圖如下:

LGHStruct02.png

結構體嵌套
struct LGHStruct03 {
    int a;
    char b;
    short c;
    float d;
    struct LGHStruct01 str01;
}struct03;
NSLog(@"%lu",sizeof(struct03));
// 打印結果是48

LGHStruct03里面嵌套了LGHStruct01,

  • 變量a:占4個字節,從0開始,min(0,4),即放在下0-4存儲
  • 變量1:占1個字節,從4開始,min(4,1),4%1==0,即放在4存儲
  • 變量c:占 2 個字節,從 5 開始,min(5,2),5%2!=0,向后移到min(6,2),6%2==0, 變量c放在6-7存儲
  • 變量d:占4字節,從8開始,min(8,4),8%4==0,即d放在8-11存儲
  • 變量str01:占32字節,但str01里面最大變量所占的字節數是8,從12開始,min(12,8),12%8!=0,向后移到1616%8==0,即e放在16-47存儲

所以LGHStruct03所需的內存是48,對齊字節數應為當前結構體或者嵌套的某個結構體成員的最大成員大小,是LGHStruct01里面的long類型為8,所以對齊數為8,而48剛好是8的倍數,所以sizeof(struct03) = 48.
如下圖所示:

LGHStruct03.png

內存優化(屬性重排)
我們知道蘋果它對OC對象的屬性的存儲是進行了重排的。下面我們定義一個LGHPerson類,來看看屬性在內存中的存儲。

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) long height;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) float hair;
@property (nonatomic, assign) short ID;
@property (nonatomic) char c1;
@property (nonatomic) char c2;
@property (nonatomic) char c3;
@property (nonatomic) char c4;
@property (nonatomic) char c5;
@end

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        person.name      = @"Cooci";
        person.nickName  = @"KC";
        person.age       = 18;
        person.height    = 170;
        person.hair = 100;
        person.ID = 10;
        person.c1        = 'a';
        person.c2        = 'b';
        person.c3        = 'c';
        person.c4        = 'd';
        person.c5        = 'e';
        NSLog(@"%lu - %@ - %lu",sizeof(float),person,malloc_size((__bridge const void *)person));
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

我們在NSLog這里打個斷點,接下來看看每個屬性具體的存放位置:


malloc_size.png

查看person對象.png

查看地址存放的內容.png

屬性存放在內存的示意圖.png

**總結: **
蘋果對OC對象的存儲進行重排,根據屬性所占的字節數的大小,從小到大排序,先存儲所占字節數較少的屬性,再存儲所占字節數較少的屬性,從而減少padding(內存占位符),達到內存優化的效果.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380