iOS底層探索 02-內(nèi)存對齊原理

一. 結(jié)構(gòu)體引入

結(jié)構(gòu)體是由基本數(shù)據(jù)類型或者指針,數(shù)組,結(jié)構(gòu)體/聯(lián)合體/枚舉等類型組成的一種結(jié)構(gòu),我們可以簡單地定義一個(gè)結(jié)構(gòu)體如下

struct Person {
  char *contact;
  bool sex;
  short int age;
  int height;
  char  name[9];
};

二. 結(jié)構(gòu)體所占內(nèi)存大小

結(jié)構(gòu)體的大小主要與操作系統(tǒng)地址總線寬度碳柱、編譯器對齊方式相關(guān)熬芜。

各種數(shù)據(jù)類型在ios中的占用內(nèi)存大小


image

在64位系統(tǒng)下探索結(jié)構(gòu)體占用的內(nèi)存大小


    struct  Person stu;
    stu.sex = NO;
    stu.age = 18;
    stu.contact = "984361822@qq.com";
    stu.height = 20;
    stu.name[0]= 65;
    stu.name[1]= 66;
    stu.name[2]= 65;
    stu.name[3]= 66;
    stu.name[4]= 65;
    stu.name[5]= 66;
    stu.name[6]= 65;
    stu.name[7]= 66;
    stu.name[8]= 65;
    printf("%lu",sizeof(stu)); //32

上面代碼打印出的stu所占內(nèi)存為32個(gè)字節(jié),

\color{#FF0000}{為啥是32個(gè)字節(jié)?}

首先將斷點(diǎn)設(shè)置在如圖位置,當(dāng)斷點(diǎn)斷住時(shí),在調(diào)試區(qū)域可以看到stu對象,右擊stu,選中View Memory of "stu",可以查看stu的內(nèi)存分配


image

從內(nèi)存地址中的值,可以觀察到stu的內(nèi)存分配:


image

成員變量在內(nèi)存中的存儲位置為:


image
  • contact; 是個(gè)指針,在64位系統(tǒng)下占用8個(gè)字節(jié)

  • sex,age,height 共同占用8個(gè)字節(jié)
    sex;實(shí)際占用一個(gè)字節(jié),但需要字節(jié)對齊,sex后會(huì)空一個(gè)字節(jié)
    age 占用2個(gè)字節(jié),
    height;占用8個(gè)字節(jié)

  • name 實(shí)際占用9個(gè)字節(jié),但會(huì)字節(jié)對齊,其后會(huì)預(yù)留7個(gè)字節(jié),以達(dá)到8字節(jié)的對齊

\color{#FF0000}{為啥編譯器會(huì)字節(jié)對齊莲镣?}

  • 平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù)涎拉,否則拋出硬件異常瑞侮。

  • 性能原因:數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對齊。原因在于曼库,為了訪問未對齊的內(nèi)存区岗,處理器需要作兩次內(nèi)存訪問;而對齊的內(nèi)存訪問僅需要一次訪問毁枯。

三. 結(jié)構(gòu)體嵌套的內(nèi)存大小


struct Age {
    int age;
    bool sex;
};
struct Human {
    char  name;
    struct Age a1;
};

結(jié)構(gòu)體Age的占用的內(nèi)存大小在64位系統(tǒng)下是8個(gè)字節(jié)慈缔,占用內(nèi)存最大的成員變量是age,占用4個(gè)字節(jié);
結(jié)構(gòu)體Human的尺寸在64位系統(tǒng)下占用12個(gè)字節(jié),由于結(jié)構(gòu)體Age中最大的成員變量是age,占用4個(gè)字節(jié);所以name占用一個(gè)字節(jié)后,將空三個(gè)自己,然后再開始存儲a1;

\color{#FF0000}{總結(jié):內(nèi)存對齊原則}

  • 數(shù)據(jù)成員對?規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個(gè)數(shù)據(jù)成員放在offset為0的地方种玛,以后每個(gè)數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員藐鹤,比如說是數(shù)組,結(jié)構(gòu)體等)的整數(shù)倍開始

比如int為4字節(jié),則要從4的整數(shù)倍地址開始存
儲瓤檐。

min(當(dāng)前開始的位置m,n)m=9n=4 其中 m表示當(dāng)前成員的開始位置, n表示當(dāng)前成員所需要的位數(shù)。如果滿足條件 m 整除 n (即 m % n == 0), n 從 m 位置開始存儲, 反之繼續(xù)檢查 m = m+1 能否整除 n, 直到可以整除, 從而就確定了當(dāng)前成員的開始位置娱节。

  • 數(shù)據(jù)成員為結(jié)構(gòu)體:當(dāng)結(jié)構(gòu)體嵌套了結(jié)構(gòu)體時(shí)挠蛉,作為數(shù)據(jù)成員的結(jié)構(gòu)體的自身長度作為外部結(jié)構(gòu)體的最大成員的內(nèi)存大小,比如結(jié)構(gòu)體a嵌套結(jié)構(gòu)體b肄满,b中有char谴古、int、double等稠歉,則b的自身長度為8

  • 最后結(jié)構(gòu)體的內(nèi)存大小必須是結(jié)構(gòu)體中最大成員內(nèi)存大小整數(shù)倍掰担,不足的需要補(bǔ)齊

案例分析:


image

四. OC類的內(nèi)存優(yōu)化

定義一個(gè)自定義CJLPerson類,并定義幾個(gè)屬性怒炸,
@interface LGPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;

@property (nonatomic) char c1;
@property (nonatomic) char c2;
@end

@implementation LGPerson

@end

在OC類中聲明的實(shí)體屬性會(huì)轉(zhuǎn)化為數(shù)據(jù)成員带饱。每個(gè)OC類中還會(huì)有一個(gè)隱式的數(shù)據(jù)成員isa,這是一個(gè)指針類型的數(shù)據(jù)成員阅羹,并且是作為類的第一個(gè)數(shù)據(jù)成員被定義勺疼。

OC類中定義的實(shí)例屬性,系統(tǒng)在編譯時(shí)會(huì)創(chuàng)建一個(gè)帶下劃線的數(shù)據(jù)成員,屬性數(shù)據(jù)成員的內(nèi)存排列順序會(huì)被優(yōu)化處理捏鱼。
存儲順序?yàn)? isa,c1,c2执庐,age,name,nickname,height

直接定義內(nèi)部的數(shù)據(jù)成員不參與排序優(yōu)化,比如下面的形式:


@implementation LGPerson {
   //內(nèi)部的數(shù)據(jù)成員
    int type;
    NSString  *sencondName;
}
@end

上面的實(shí)現(xiàn)中定義了兩個(gè)內(nèi)部數(shù)據(jù)成員type,sencondName穷躁。當(dāng)出現(xiàn)這種情況時(shí)編譯器不會(huì)對這些內(nèi)部數(shù)據(jù)成員的順序進(jìn)行優(yōu)化耕肩,而是按定義的順序在內(nèi)存中進(jìn)行排列,并且是優(yōu)先于屬性數(shù)據(jù)成員進(jìn)行排列,type和sencondName排列在isa之后,其他實(shí)例屬性之前.

存儲順序?yàn)? isa,type,sencondName,c1,c2问潭,age,name,nickname,height
注意:type會(huì)占用8個(gè)字節(jié)

五. iOS中獲取內(nèi)存大小的三種方式

  • sizeof:操作符,傳入的可以是數(shù)據(jù)類型和對象,編譯階段就可以確定其大小
  • class_getInstanceSize:用于獲取類的實(shí)例對象所占用的內(nèi)存大小,并返回具體的字節(jié)數(shù)婚被,其本質(zhì)就是獲取實(shí)例對象中成員變量的內(nèi)存大小,obj4中會(huì)進(jìn)行8字節(jié)對齊
/** 
 * Returns the size of instances of a class.
 * 
 * @param cls A class object.
 * 
 * @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil.
 */
OBJC_EXPORT size_t
class_getInstanceSize(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);



size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}



// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}



static inline uint32_t word_align(uint32_t x) {
    //8字節(jié)對齊 #define WORD_MASK 7UL
    return (x + WORD_MASK) & ~WORD_MASK;
}


  • malloc_size:獲取系統(tǒng)實(shí)際分配的內(nèi)存大小,64位系統(tǒng)下會(huì)進(jìn)行16字節(jié)對齊

六. 內(nèi)存對齊

6.1#pragma pack(n)

每個(gè)平臺上的編譯器都有自己的默認(rèn)“對齊系數(shù)”(也叫對齊模數(shù))狡忙。程序員可以通過預(yù)編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數(shù)址芯,在ios中灾茁,Xcode默認(rèn)為#pragma pack(8),即8字節(jié)對齊
通過預(yù)編譯命令#pragma pack()取消自定義字節(jié)對齊方式

#pragma pack(1) //編譯器1字節(jié)對齊存儲
struct Person
{
char sex;
short age;
float weight;
char c1
};
#pragma pack() //取消1字節(jié)對齊谷炸,恢復(fù)默認(rèn)對齊
//sizeof(Person)  = 8

struct Human
{
char sex;
short age;
float weight;
char c1;
};

//sizeof(Human) = 12

6.2 __attribute__((aligned (n)))

__attribute__((aligned (n)))北专,讓所作用的結(jié)構(gòu)成員對齊在n字節(jié)自然邊界上。如果結(jié)構(gòu)中有成員的長度大于n旬陡,則按照最大成員的長度來對齊拓颓。

__attribute__((packed)),取消結(jié)構(gòu)在編譯過程中的優(yōu)化對齊描孟,按照實(shí)際占用字節(jié)數(shù)進(jìn)行對齊驶睦。

#define PACKED __attribute__((packed))
struct PACKED Person {//取消字節(jié)對齊
    char sex;
    short age;
    float weight;
};
#pragma pack() //恢復(fù)默認(rèn)對齊
//sizeof(Person)  = 7 ,其中sex占一個(gè)字節(jié)砰左,age占兩個(gè)字節(jié),weight占4個(gè)字節(jié)

#define PACKED8 __attribute__((aligned (8)))
struct PACKED8 Student{
    char sex;
    short age;
    float weight;
    char c1;
};
#pragma pack() //恢復(fù)默認(rèn)對齊
//sizeof(Student)  = 16  其中sex占一個(gè)字節(jié)场航,空隙1個(gè)字節(jié)缠导,age占兩個(gè)字節(jié),weight占4個(gè)字節(jié)溉痢,c1占一個(gè)字節(jié)僻造,末尾空隙7個(gè)字節(jié)


struct Human{
    char sex;
    short age;
    float weight;
    char c1;
};
    //sizeof(Human) = 12 ,其中sex占一個(gè)字節(jié),空隙1個(gè)字節(jié)孩饼,age占兩個(gè)字節(jié)髓削,weight占4個(gè)字節(jié),c1占一個(gè)字節(jié)捣辆,末尾空隙三個(gè)字節(jié)

可以定義一個(gè)Student對象來斷點(diǎn)查看下內(nèi)存:

 struct Student stu;
    stu.sex = 'M';
    stu.age = 18;
    stu.weight= 50.0;
    stu.c1 = 'A';

內(nèi)存的存儲情況如下:sex占一個(gè)字節(jié)蔬螟,空隙1個(gè)字節(jié),age占兩個(gè)字節(jié)汽畴,weight占4個(gè)字節(jié)旧巾,c1占一個(gè)字節(jié),末尾空隙7個(gè)字節(jié)
[圖片上傳失敗...(image-b91ff4-1600327993148)]

6.3 16字節(jié)對齊算法1

static inline size_t align16(size_t x) {
   return (x + size_t(15)) & ~size_t(15);
}

6.4 16字節(jié)對齊算法2

#define SHIFT_NANO_QUANTUM      4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM)   // 16

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
    size_t k, slot_bytes;

    if (0 == size) {
        size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           // multiply by power of two quanta size
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}

6.5 8字節(jié)對齊算法

static inline uint32_t word_align(uint32_t x) {
  // 8字節(jié)對齊 #define WORD_MASK 7UL
    return (x + WORD_MASK) & ~WORD_MASK;
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末忍些,一起剝皮案震驚了整個(gè)濱河市鲁猩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌罢坝,老刑警劉巖廓握,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嘁酿,居然都是意外死亡隙券,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門闹司,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娱仔,“玉大人,你說我怎么就攤上這事游桩∩龋” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵借卧,是天一觀的道長盹憎。 經(jīng)常有香客問我,道長铐刘,這世上最難降的妖魔是什么陪每? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上奶稠,老公的妹妹穿的比我還像新娘俯艰。我一直安慰自己,他們只是感情好锌订,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布竹握。 她就那樣靜靜地躺著,像睡著了一般辆飘。 火紅的嫁衣襯著肌膚如雪啦辐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天蜈项,我揣著相機(jī)與錄音芹关,去河邊找鬼。 笑死紧卒,一個(gè)胖子當(dāng)著我的面吹牛侥衬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跑芳,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼轴总,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了博个?” 一聲冷哼從身側(cè)響起怀樟,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盆佣,沒想到半個(gè)月后往堡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡共耍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年虑灰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痹兜。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瘩缆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出佃蚜,到底是詐尸還是另有隱情,我是刑警寧澤着绊,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布谐算,位于F島的核電站,受9級特大地震影響归露,放射性物質(zhì)發(fā)生泄漏洲脂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望恐锦。 院中可真熱鬧往果,春花似錦、人聲如沸一铅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潘飘。三九已至肮之,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卜录,已是汗流浹背戈擒。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留艰毒,地道東北人筐高。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像丑瞧,于是被迫代替她去往敵國和親柑土。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355