iOS之底層內(nèi)存對齊

引言

內(nèi)存對齊是內(nèi)存里面一個(gè)很重要的詞匯,可是大部分開發(fā)者對這個(gè)詞匯的含義都是一知半解。

 WJPerson*wj = [WJPerson alloc];
wj.name = @"無極";
wj.age = 30;
NSLog(@"對象類型的內(nèi)存大?。?lu",sizeof(wj));
NSLog(@"對象實(shí)際的內(nèi)存大?。?lu",class_getInstanceSize([wj class]));
NSLog(@"對象分配的內(nèi)存大?。?lu",malloc_size((__bridge const void *)(wj)));
NSLog(@"-----------------------------------------");

WJPerson*wj2;
NSLog(@"對象類型的內(nèi)存大?。?lu",sizeof(wj2));
NSLog(@"對象實(shí)際的內(nèi)存大?。?lu",class_getInstanceSize([wj2 class]));
NSLog(@"對象分配的內(nèi)存大?。?lu",malloc_size((__bridge const void *)(wj2)));

輸出結(jié)果:

2021-06-16 13:12:08.712404+0800 內(nèi)存對齊[3440:72350] 對象類型的內(nèi)存大?。?
2021-06-16 13:12:08.712537+0800 內(nèi)存對齊[3440:72350] 對象實(shí)際的內(nèi)存大小:24
2021-06-16 13:12:08.712659+0800 內(nèi)存對齊[3440:72350] 對象分配的內(nèi)存大?。?2
2021-06-16 13:12:08.712746+0800 內(nèi)存對齊[3440:72350] -----------------------------------------
2021-06-16 13:12:08.712831+0800 內(nèi)存對齊      [3440:72350] 對象類型的內(nèi)存大?。?
2021-06-16 13:12:08.712923+0800 內(nèi)存對齊[3440:72350] 對象實(shí)際的內(nèi)存大小:0
2021-06-16 13:12:08.713007+0800 內(nèi)存對齊[3440:72350] 對象分配的內(nèi)存大?。?

結(jié)果分析:

  • sizeof:對象類型的內(nèi)存大小,sizeof是用來計(jì)算一個(gè)變量或者一個(gè)常量、一種數(shù)據(jù)類型所占的內(nèi)存字節(jié)數(shù)。自定義對象的本質(zhì)是結(jié)構(gòu)體指針,所以占8個(gè)字節(jié)。
  • class_getInstanceSize:對象實(shí)際(對齊后)的內(nèi)存大小,內(nèi)存大小是由類的成員變量的大小決定的。實(shí)際上并不是嚴(yán)格意義上的對象的內(nèi)存的大小,因?yàn)閮?nèi)存進(jìn)行了8字節(jié)對齊,所以wj的內(nèi)存大小是24而不是20。而wj2只是聲明變量,并沒有走alloc方法開辟內(nèi)存,所以大小是0。核心內(nèi)存大小算法是:define WORD_MASK 7UL ((x + WORD_MASK) & ~WORD_MASK
  • malloc_size:系統(tǒng)實(shí)際分配的內(nèi)存大小,以16字節(jié)對齊,不足16的自動補(bǔ)齊。注意:系統(tǒng)的16字節(jié)對齊是在實(shí)際的內(nèi)存大小(經(jīng)過8字節(jié)對齊后)的基礎(chǔ)上。上面的wj對象實(shí)際內(nèi)存大小24字節(jié),不是16的倍數(shù),所以系統(tǒng)實(shí)際分配為32。

問題:class_getInstanceSizemalloc_size 底層做了什么?我們?nèi)绾沃?code>class_getInstanceSize是8字節(jié)對齊,而malloc_size16字節(jié)對齊?

在研究后面重點(diǎn)之前,我們先來看下基本數(shù)據(jù)類型在arm64環(huán)境下占用的內(nèi)存大小。

基本數(shù)據(jù)類型所占字節(jié)數(shù).gif

下面解釋為什么計(jì)算機(jī)會有內(nèi)存對齊的概念,出于什么目的要內(nèi)存對齊。

  • 內(nèi)存是以字節(jié)為基本單位,cpu在讀取數(shù)據(jù)時(shí),是以為單位讀取,并不是以字節(jié)為單位讀取。頻繁讀取未對齊的數(shù)據(jù),會加大cpu的開銷。字節(jié)對齊后,會降低cpu的存取次數(shù),這種以空間時(shí)間的做法降低了cpu的開銷。
  • cpu存?。菏且?code>塊為單位,存取未對齊的數(shù)據(jù)可能開始在上一個(gè)內(nèi)存塊,結(jié)束在另一個(gè)內(nèi)存塊。這樣中間可能要經(jīng)過復(fù)雜的運(yùn)算在合并在一起,降低了效率,字節(jié)對齊后,提高了cpu的訪問效率。

內(nèi)存對齊規(guī)則:

數(shù)據(jù)成員對齊規(guī)則:結(jié)構(gòu)體(struct)(或聯(lián)合體(union))的數(shù)據(jù)成員,第一個(gè)數(shù)據(jù)成員放在offset為0的地方(即首地址的位置),以后每個(gè)數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大?。ㄖ灰摮蓡T有子成員,比如說是數(shù)組,結(jié)構(gòu)體等)的整數(shù)倍開始(比如int4字節(jié)),則要從4的整數(shù)倍地址開始存儲。
結(jié)構(gòu)體作為成員變量:如果一個(gè)結(jié)構(gòu)體里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍開始存儲(struct a里有struct b,b里有char,int,double等元素,那b應(yīng)該從8(doudle為8 )整數(shù)倍開始存儲)

下面我們先來看一個(gè)例子:


struct內(nèi)存.gif
struct WJPerson1{
double a;       
char b;       
int c;          
short d;
}myPerson1;
struct WJPerson2
{
double b;
int c;
char a;
short d;
}myPerson2;
NSLog(@"%lu-%lu",sizeof(myPerson1),sizeof(myPerson2));

輸出結(jié)果:

 2021-06-16 16:13:21.621334+0800 內(nèi)存對齊[4074:158189] 24-16

從上面我們可以看出,myPerson1myPerson2兩個(gè)結(jié)構(gòu)體里面元素是一樣的,只是順序不同,內(nèi)存大小卻不一樣,為什么?這就是結(jié)構(gòu)體內(nèi)存對齊。

具體分析如下:[p,q] p表示當(dāng)前開始的位置,q表示大小
myPerson1:

  • double a: [0,7] 即(0~7存放a)
  • char b:[8,1] 即(8存放b)
  • int c:[12,15] 即(9,10,11不是int 4得出倍數(shù),廢棄,12~15存放c)
  • short d:[16,2] 即(16 ~ 17存放d)

myPerson2:

  • double a: (0,7) 即(0~7存放a)
  • int b:(8,4) 即(8~11存放b)
  • char c:(12,1) 即(12存放c)
  • short d:(14,2) 即(13不是d( short 2的倍數(shù))位置廢棄,14 ~ 15存放d)

下面這個(gè)是嵌套的結(jié)構(gòu)體
struct WJPerson3 {
double a;
int b;
char c;
short d;
int e;
struct WJPerson1 str;
} myPerson3;


struct嵌套內(nèi)存.gif

myPerson3具體分析如下:

  • double a: [0,7] 即(0~7存放a)
  • char b:[8,1] 即(8存放b)
  • int c:[12,15] 即(9,10,11不是int 4得出倍數(shù),廢棄,12~15存放c)
  • short d:[16,2] 即(16 ~ 17存放d)
  • int e:(20,4) 即(18,19不是int 4得出倍數(shù),廢棄.20 ~ 23存放d)
  • 變量str: str是結(jié)構(gòu)體變量,內(nèi)存對齊原則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲。WJPerson1 中的最大的變量a( double)占8字節(jié),所以offset從24開始,WJPerson1的內(nèi)存大小是18字節(jié)。[24,18],即24 ~ 42存放 str,計(jì)算出來的是42個(gè)字節(jié),但是myPerson3中最大的變量是str和 a都是 8 字節(jié),所以myPerson3的實(shí)際內(nèi)存大小必須是8的整數(shù)倍,42不是8的整數(shù)倍,因此補(bǔ)齊應(yīng)該是48.
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容